Gather couldn't connect to its database. This is usually temporary β but if it persists, you can verify or re-enter the database credentials below.
Go to supabase.com, create a free account, and start a new project.
In your project, open the SQL Editor and run this to create the tables:
create table if not exists events (
id uuid primary key default gen_random_uuid(),
title text not null,
datetime timestamptz,
location text,
description text,
color text default '#F59E0B',
created_at timestamptz default now()
);
create table if not exists items (
id uuid primary key default gen_random_uuid(),
event_id uuid references events(id) on delete cascade,
name text not null,
qty int default 1,
claimed_by text,
sort_order int default 0
);
create table if not exists rsvps (
id uuid primary key default gen_random_uuid(),
event_id uuid references events(id) on delete cascade,
name text not null,
email text,
note text,
status text check (status in ('yes','maybe','no')),
created_at timestamptz default now()
);
-- Allow public read/write (no auth required for guests)
alter table events enable row level security;
alter table items enable row level security;
alter table rsvps enable row level security;
create policy "public read events" on events for select using (true);
create policy "public insert events" on events for insert with check (true);
create policy "public update events" on events for update using (true);
create policy "public delete events" on events for delete using (true);
create policy "public read items" on items for select using (true);
create policy "public insert items" on items for insert with check (true);
create policy "public update items" on items for update using (true);
create policy "public delete items" on items for delete using (true);
create policy "public read rsvps" on rsvps for select using (true);
create policy "public insert rsvps" on rsvps for insert with check (true);
create policy "users update own rsvps" on rsvps for update using (auth.uid() = user_id);
-- Comments on events
create table if not exists comments (
id uuid primary key default gen_random_uuid(),
event_id uuid references events(id) on delete cascade,
user_id uuid references auth.users(id),
body text not null,
created_at timestamptz default now()
);
alter table comments enable row level security;
create policy "public read comments" on comments for select using (true);
create policy "signed in users insert comments" on comments
for insert with check (auth.uid() is not null and auth.uid() = user_id);
create policy "users delete own comments" on comments
for delete using (auth.uid() = user_id);
create policy "public delete comments admin" on comments
for delete using (true);
-- Comments on public profiles ("What others say aboutβ¦")
create table if not exists profile_comments (
id uuid primary key default gen_random_uuid(),
profile_id uuid not null references auth.users(id) on delete cascade,
user_id uuid not null references auth.users(id),
body text not null,
created_at timestamptz default now(),
-- Prevent the profile owner from commenting on their own profile
constraint no_self_comment check (profile_id != user_id)
);
alter table profile_comments enable row level security;
create policy "public read profile comments" on profile_comments for select using (true);
create policy "signed in non-owners insert profile comments" on profile_comments
for insert with check (auth.uid() is not null and auth.uid() = user_id and auth.uid() != profile_id);
create policy "users delete own profile comments" on profile_comments
for delete using (auth.uid() = user_id);
create policy "public delete profile comments admin" on profile_comments
for delete using (true);
-- Shared admin password (stored as a SHA-256 hash, synced across all browsers)
create table if not exists settings (
key text primary key,
value text
);
alter table settings enable row level security;
create policy "public read settings" on settings for select using (true);
create policy "public write settings" on settings for insert with check (true);
create policy "public update settings" on settings for update using (true);
-- User accounts: link RSVPs and item claims to a logged-in user (optional, nullable)
alter table rsvps add column if not exists user_id uuid references auth.users(id);
alter table items add column if not exists claimed_by_user_id uuid references auth.users(id);
-- Let logged-in users update their own RSVP instead of creating duplicates
drop policy if exists "users update own rsvps" on rsvps;
create policy "users update own rsvps" on rsvps for update using (auth.uid() = user_id);
-- Require sign-in to RSVP or claim/add items (guests can still browse & read everything)
drop policy if exists "public insert rsvps" on rsvps;
create policy "signed in users insert rsvps" on rsvps
for insert with check (auth.uid() is not null and auth.uid() = user_id);
drop policy if exists "public insert items" on items;
create policy "signed in users insert items" on items
for insert with check (
auth.uid() is not null and (claimed_by_user_id is null or claimed_by_user_id = auth.uid())
);
-- Item updates stay open (admins edit item lists via a separate app-level password,
-- not Supabase Auth, so this can't be locked to auth.uid() without breaking that).
-- Sign-in for claiming is enforced in the app UI via requireUser().
-- Block RSVPs and item sign-ups for events whose date has already passed.
-- (An event with no datetime set is never considered "past".)
drop policy if exists "signed in users insert rsvps" on rsvps;
create policy "signed in users insert rsvps" on rsvps
for insert with check (
auth.uid() is not null and auth.uid() = user_id
and not exists (
select 1 from events where events.id = rsvps.event_id
and events.datetime is not null and events.datetime < now()
)
);
drop policy if exists "users update own rsvps" on rsvps;
create policy "users update own rsvps" on rsvps
for update using (
auth.uid() = user_id
and not exists (
select 1 from events where events.id = rsvps.event_id
and events.datetime is not null and events.datetime < now()
)
);
drop policy if exists "signed in users insert items" on items;
create policy "signed in users insert items" on items
for insert with check (
auth.uid() is not null and (claimed_by_user_id is null or claimed_by_user_id = auth.uid())
and not exists (
select 1 from events where events.id = items.event_id
and events.datetime is not null and events.datetime < now()
)
);
-- Allow RSVPs to be deleted (used by event admins to remove a guest's RSVP).
-- Admin access is gated by the app's own password system, not Supabase Auth,
-- so this stays open the same way events/items deletes already are.
create policy "public delete rsvps" on rsvps for delete using (true);
-- Google Maps link for the event location, and an optional cap on "yes" RSVPs
alter table events add column if not exists maps_url text;
alter table events add column if not exists album_url text;
alter table events add column if not exists rsvp_limit integer;
alter table events add column if not exists badge_emoji text default 'π';
alter table events add column if not exists badge_label text;
-- Helper function to count confirmed ("yes") RSVPs for an event.
-- SECURITY DEFINER lets it bypass RLS internally so it can be safely called
-- from inside an RLS policy on the same table without recursion issues.
create or replace function public.yes_rsvp_count(p_event_id uuid, p_exclude_rsvp_id uuid default null)
returns integer
language sql
security definer
set search_path = public
as $$
select count(*)::integer from rsvps
where event_id = p_event_id
and status = 'yes'
and (p_exclude_rsvp_id is null or id != p_exclude_rsvp_id);
$$;
-- Enforce the RSVP limit at the database level too (only "yes" RSVPs count
-- against the cap; events with no limit set are unrestricted).
drop policy if exists "signed in users insert rsvps" on rsvps;
create policy "signed in users insert rsvps" on rsvps
for insert with check (
auth.uid() is not null and auth.uid() = user_id
and not exists (
select 1 from events where events.id = rsvps.event_id
and events.datetime is not null and events.datetime < now()
)
and (
status != 'yes' or not exists (
select 1 from events
where events.id = rsvps.event_id
and events.rsvp_limit is not null
and events.rsvp_limit <= public.yes_rsvp_count(rsvps.event_id)
)
)
);
drop policy if exists "users update own rsvps" on rsvps;
create policy "users update own rsvps" on rsvps
for update using (
auth.uid() = user_id
and not exists (
select 1 from events where events.id = rsvps.event_id
and events.datetime is not null and events.datetime < now()
)
)
with check (
status != 'yes' or not exists (
select 1 from events
where events.id = rsvps.event_id
and events.rsvp_limit is not null
and events.rsvp_limit <= public.yes_rsvp_count(rsvps.event_id, rsvps.id)
)
);
-- Cover photo: a pasted image URL shown on event cards and the event page
-- (no file upload/storage needed β same lightweight pattern as the Maps/Album links).
alter table events add column if not exists cover_image_url text;
-- Event categories: a shared lookup table so events can be browsed by type.
-- Public read + insert; category management is gated by the admin password in the UI.
create table if not exists categories (
id uuid primary key default gen_random_uuid(),
name text not null,
created_at timestamptz default now(),
constraint categories_name_unique unique (name)
);
alter table categories enable row level security;
drop policy if exists "public read categories" on categories;
create policy "public read categories" on categories for select using (true);
drop policy if exists "public insert categories" on categories;
create policy "public insert categories" on categories for insert with check (true);
alter table events add column if not exists category_id uuid references categories(id);
alter table events add column if not exists payment text;
alter table events add column if not exists what_to_bring text;
-- Event teams: named groups for competitive/split-group events.
-- Admin-managed in the event editor; RSVPs can be assigned to a team.
create table if not exists event_teams (
id uuid primary key default gen_random_uuid(),
event_id uuid not null references events(id) on delete cascade,
name text not null,
color text default '#FF6B4A',
sort_order integer default 0,
created_at timestamptz default now()
);
alter table event_teams enable row level security;
drop policy if exists "public read teams" on event_teams;
create policy "public read teams" on event_teams for select using (true);
drop policy if exists "public insert teams" on event_teams;
create policy "public insert teams" on event_teams for insert with check (true);
drop policy if exists "public update teams" on event_teams;
create policy "public update teams" on event_teams for update using (true);
drop policy if exists "public delete teams" on event_teams;
create policy "public delete teams" on event_teams for delete using (true);
alter table rsvps add column if not exists team_id uuid references event_teams(id) on delete set null;
insert into categories (name) values
('Birthday'), ('Brunch'), ('Beach day'), ('Dinner'),
('Drinks'), ('Game night'), ('Hiking'), ('Karaoke'),
('Movie night'), ('Potluck')
on conflict on constraint categories_name_unique do nothing;
Already have the app running? Just run the new block(s) above in your existing project's SQL Editor β no need to redo the rest.
β οΈ This block blocks RSVPs and item sign-ups for events whose date has already passed, allows RSVPs to be deleted, adds a Google Maps link field, a cover photo URL field, and enforces an optional RSVP cap β all at the database level, using a true UTC comparison.
-- Public profiles: a publicly-readable table mirroring each user's display
-- name, bio, and avatar color, since auth.users itself isn't queryable by
-- other users from the client. Kept in sync via sync_public_profile(), which
-- the app calls any time the signed-in user updates their name/bio/color.
create table if not exists public_profiles (
user_id uuid primary key references auth.users(id) on delete cascade,
name text,
bio text,
avatar_color text default '#F59E0B',
updated_at timestamptz default now()
);
alter table public_profiles enable row level security;
create policy "public read profiles" on public_profiles for select using (true);
create or replace function public.sync_public_profile()
returns void
language plpgsql
security definer
set search_path = public
as $$
declare
u record;
begin
select id, raw_user_meta_data into u from auth.users where id = auth.uid();
if u.id is null then
raise exception 'Not authenticated';
end if;
insert into public_profiles (user_id, name, bio, avatar_color, updated_at)
values (
u.id,
coalesce(u.raw_user_meta_data->>'name', split_part((select email from auth.users where id = u.id), '@', 1)),
u.raw_user_meta_data->>'bio',
coalesce(u.raw_user_meta_data->>'avatar_color', '#F59E0B'),
now()
)
on conflict (user_id) do update
set name = excluded.name, bio = excluded.bio, avatar_color = excluded.avatar_color, updated_at = now();
end;
$$;
grant execute on function public.sync_public_profile() to authenticated;
-- Returns badges earned by a specific user: one badge per past event they RSVPd "yes" to.
-- Uses SECURITY DEFINER so it can safely join rsvps + events without RLS interference.
create or replace function public.get_earned_badges(p_user_id uuid)
returns table (
event_id uuid,
event_title text,
event_color text,
event_datetime timestamptz,
badge_emoji text,
badge_label text
)
language sql
security definer
set search_path = public
as $$
select
e.id as event_id,
e.title as event_title,
coalesce(e.color, '#F59E0B') as event_color,
e.datetime as event_datetime,
coalesce(e.badge_emoji, 'π') as badge_emoji,
coalesce(e.badge_label, e.title) as badge_label
from rsvps r
join events e on e.id = r.event_id
where r.user_id = p_user_id
and r.status = 'yes'
and e.datetime is not null
and e.datetime < now()
order by e.datetime desc;
$$;
grant execute on function public.get_earned_badges(uuid) to anon, authenticated;
-- Allows admin to assign RSVPs to teams without Supabase Auth role β bypasses
-- the "users update own rsvps" RLS policy which only permits self-updates.
create or replace function public.assign_rsvp_team(p_rsvp_id uuid, p_team_id uuid)
returns void
language sql
security definer
set search_path = public
as $$
update rsvps set team_id = p_team_id where id = p_rsvp_id;
$$;
grant execute on function public.assign_rsvp_team(uuid, uuid) to anon, authenticated;
Powers public profile pages, viewable by clicking a name on the RSVP list.
β οΈ IMPORTANT β one-time fix for existing events: earlier versions of this app stored datetimes as if they were UTC without actually converting them, so any events created before this update are now mislabeled. Run this once to correct existing rows (adjust the offset to match your timezone, e.g. replace '7 hours' with your UTC offset):
-- Run ONCE to fix events created before this timestamp-handling update.
-- Replace '7 hours' with your local UTC offset (e.g. Pacific = 7 or 8 hours
-- depending on daylight saving; Eastern = 4 or 5; etc). This shifts old
-- mislabeled datetimes to their correct true-UTC equivalents.
update events
set datetime = datetime + interval '7 hours'
where datetime is not null;
Go to Project Settings β API. Copy your Project URL and anon public key below.
Your credentials are saved in this browser only and never sent anywhere else.
Loadingβ¦
Event Manager
Create, edit and manage your gatherings.
Upcoming Events
My Events
Everything you've RSVP'd to or signed up to bring something for.
About Galabanting
What this is, and how it works.
π Why "Galabanting"?
I dunno. Its gallivanting but said in a Tagalog accent.
β¨ What does "Galabanting" actually mean?
You might hear it from a Filipino tita (aunt) referring to someone going out too much, like: βInstead of doing your homework/being productive, you're out gallivanting with your friends!β It often carries the vibe of βlakwatsaβ or βgalaββTagalog terms for going out and roaming around for fun
π How to use in a sentence
"There you go again! Always wondering off, galibanting with your friends instead of staying home"
Account Settings
Manage your account and admin access.
π RSVPs
π§Ί Sign-up List
π¬ Comments
Create an Event
Fill in the details β you can share the link with guests after.
Paste a link to an image (Imgur, Google Photos, etc.) β shown on the event card and event page.
If set, guests can tap the location name to open it in Google Maps.
Link to a shared album β shown on the event page after it's created.
Caps the number of guests who can RSVP "Yes."
Edit Event
Update the details below.
Paste a link to an image (Imgur, Google Photos, etc.) β shown on the event card and event page.
If set, guests can tap the location name to open it in Google Maps.
Link to a shared album β shown on the event page after it's created.
Caps the number of guests who can RSVP "Yes."
Existing claimed items are shown but can't be removed. Unclaimed items can be deleted or renamed.
RSVP
Sign Up to Bring Something
Pick one or more items you'll contribute, or add something new.
Admin Sign In
Enter the admin password to create and manage events.