What it is
An ephemeral, university-themed matching app where verified students join a 'course' queue, get paired with another eligible student, chat in a private room, and grade the interaction afterward.
Full-Stack Web App
A Next.js + Supabase social app for university students: themed course queues, atomic PL/pgSQL matching, private realtime chat, reporting, and post-match grading.
Full-Stack Web App
Overview
A recruiter-friendly breakdown of the product goal, audience, and engineering direction.
An ephemeral, university-themed matching app where verified students join a 'course' queue, get paired with another eligible student, chat in a private room, and grade the interaction afterward.
Built around verified university students. The original requirements target UBC, while the schema and signup flow appear to support broader university-domain accounts.
Existing social apps mix anonymous users and long-lived profiles. HHU explores a short-lived, identity-scoped pairing loop where both eligibility and data retention are intentionally narrow.
Problem
A casual, university-only matching loop has constraints that general-purpose social apps don't handle well: trusted eligibility, short-lived state, safe 1:1 chat, and a way to flag bad actors after the interaction ends.
Solution
Model the loop as queue → match → private chat → grade, then push integrity-critical pieces (eligibility, matching, authorization) into Supabase RPCs and RLS instead of the client.
Architecture
A high-level system view that explains the app layers and data flow without hiding the engineering decisions.
Next.js App Router with TypeScript and Tailwind CSS v4. Server components handle access control and data loading; client components drive auth, profile setup, queueing, chat, reporting, and grading.
Next.js Route Handlers and Server Actions wrapped with Zod validation, signed double-submit CSRF tokens, match-authorization checks, and Upstash-based rate limiting (lib/security/).
Supabase PostgreSQL with RLS across profiles, matches, messages, queues, ratings, and avatar storage. Core operations (enrol_course, report_match, submit_grade, get_partner_profile) are exposed as PL/pgSQL RPCs.
Supabase Auth with email/password, university-domain checks, an age gate, email verification, profile-completion gating, suspension routing, and a waitlist screen for unsupported universities.
Targets a Vercel-style Next.js deployment (uses @vercel/functions). Live deployment status is unconfirmed, and the daily_reset_job cron is documented as a manual step in Supabase.
Client submits a Zod-validated request with a CSRF token.
Middleware enforces auth, email verification, and account-state routing.
API route calls a Supabase RPC under the user's session.
RPC runs under RLS, with row-level locking on matching operations.
Realtime subscriptions broadcast new chat messages, with a polling fallback.
Features
Email/password auth with Zod-validated domain rules and an age-gate API that looks up minimum age per university.
Setup form for display name, department, gender identity, and avatar — with client-side compression, server MIME validation, and Supabase Storage policies.
Themed 'course' queues drive the matching flow. BEER 101 is the active course in the current UI; additional drink courses are scaffolded but disabled.
1:1 matched chat with optimistic UI, Supabase Realtime subscriptions, and a polling fallback so messages still arrive when realtime drops.
In-chat report modal that posts a payload to a Formspree endpoint and updates match status through a Supabase RPC.
Transcript-style grading screen that maps grades to GPA-style points and submits them via a typed submit_grade RPC.
Engineering
Problem
Two clients enrolling in the same course at the same instant could both be matched to a third user, or to each other twice.
Resolution
Move matching into a PL/pgSQL RPC (enrol_course) that uses SELECT ... FOR UPDATE SKIP LOCKED on the queue, so concurrent calls deterministically pair without holding contended locks.
Problem
A social app where users read partner profiles, send messages, file reports, and submit grades has many surfaces where a malicious client could escalate access.
Resolution
Centralize security in lib/security/: authedJsonRoute (Zod), withCsrfProtect (signed double-submit), and authz.ts (match participation / partner access). RLS and a dedicated get_partner_profile RPC enforce the same rules at the database layer.
Problem
Supabase Realtime can drop subscriptions on flaky networks, which is unacceptable for a chat surface users only see for a few minutes.
Resolution
Subscribe to message inserts for low-latency updates, but keep a polling fallback in chat-room.tsx so messages still appear if the realtime channel disconnects.