Pulse Labs

multi-tenant · rls · analytics

github ↗︎ try it here ↗︎

i’m the vice president of the computer science society at tcu, and i originally built pulse labs because i got tired of switching between spreadsheets, google forms, and paper sign-in sheets every time we ran an event. i wanted one place where we could manage members, organize events, and track attendance. after i deployed the first version, people started asking for more features, and the project slowly grew into a platform for student organizations.

it’s built with react (tanstack start), typescript, postgresql, and supabase. i decided not to build a separate backend service. instead, most of the authorization lives in the database through row level security and postgres functions. every organization has its own workspace, and access is enforced in the database instead of relying on frontend checks.

organizations members event_checkins Postgres + RLS lock React app anon key qr check-in token rotates / 5 min no separate backend

supporting multiple organizations ended up changing a lot of the project. once every club had its own members, officers, events, and invitations, i had to redesign a good part of the database. permissions became much more complicated than they were in the first version, and moving them into postgres made the rest of the application much simpler.

organizations members events org_staff invitations one organizations row, four dependent tables

one bug i found pretty late was in my row level security policies. some of the earliest policies were written before the app supported multiple organizations, and they still allowed public reads. the interface never exposed another organization’s data, but someone could still query it directly with the public api key. i rewrote those policies and moved the public check-in flow into a small set of postgres functions that only expose the data needed for that page.

request anon api key RLS policy is_org_staff(org_id) checked on every table, not just the ones the ui uses match rows scoped to that org no match empty result set

qr check-ins changed a lot too. the first version used a static qr code, which meant anyone with a screenshot could keep using it. now every qr code rotates every five minutes using an hmac based on the event and the current time window. resetting a session immediately invalidates every previous qr code without affecting attendance records.

officer opens qr dialog hmac(event_id, 5-min window) reset invalidates old tokens instantly, no attendance change attendee scans, checks in

there isn’t an analytics table either. attendance, retention, active members, and engagement metrics are all calculated directly from check-in data whenever someone opens the dashboard. i originally tried storing some of those values, but they eventually became inconsistent, so i removed them and calculated everything from the source data instead.

the computer science society was the first organization to use pulse labs. after it was deployed, most of the work shifted from adding new features to improving the ones that already existed. i still use it whenever we run events, so every semester i end up finding something else to improve.