PeerGrid is a mobile-first, verified student network for Newton School of Technology students in Bangalore, Pune, Delhi NCR, and Hyderabad.
The current product is focused on five jobs:
- create a useful verified student profile;
- discover students by campus, skills, interests, and goals;
- follow students and track followers;
- publish, like, and comment on text, image, video, and document posts;
- publish and browse lightweight collaboration calls.
- Next.js 16 App Router, React 19, TypeScript, Tailwind CSS 4
- Supabase Auth, Postgres, Row Level Security, and Storage
@supabase/ssrfor cookie-backed browser/server sessions
-
Install dependencies with
npm install. -
Copy
.env.exampleto.env.localand add the Supabase project URL and publishable key. -
Link the Supabase CLI project and run
supabase db pushto apply every migration. If you use the SQL Editor instead, run the files insupabase/migrationsin filename order, including20260831000000_production_readiness.sql. -
For the initial manual-approval phase, leave
public.allowed_email_domainsempty. Signup remains open, but no confirmed user can enter the network until an administrator approves them. -
Later, when the real NST domain is confirmed, add it to
public.allowed_email_domains:insert into public.allowed_email_domains (domain) values ('the-confirmed-student-domain.edu');
-
In hosted Supabase Auth Hooks, enable the Before User Created Postgres hook and select
public.hook_restrict_signup_by_email_domain. The includedsupabase/config.tomlenables it automatically for local Supabase. -
Keep email confirmation enabled and add
/auth/callbackto the allowed redirect URLs. -
Start the app with
npm run dev.
No email domain is hardcoded or seeded. With no active domain rows, email-confirmed accounts enter the manual review queue.
New accounts follow this flow:
sign up → confirm email → pending approval → profile onboarding → PeerGrid
To approve a student in the Supabase Dashboard:
- Open Table Editor → student_approvals.
- Confirm the email belongs to an NST student.
- Change
statusfrompendingtoapprovedand save. The database fillsreviewed_atautomatically.
To reject an account, set status to rejected and optionally add a short review_note.
The equivalent SQL is:
update public.student_approvals
set status = 'approved'
where email = 'student@example.edu';Later automation only needs to update this same approval row; application authorization does not need to change.
app/(platform)contains the protected V1 routes and shared application shell.app/authcontains login, signup, email verification, and PKCE callback routes.app/actionscontains authenticated server mutations.app/lib/supabasecontains separate browser, server, and session-refresh clients.supabase/migrationsis the source of truth for tables, indexes, constraints, hooks, grants, RLS, and avatar storage policies.
The public client uses only the Supabase publishable key. Never add a service-role key to a NEXT_PUBLIC_ environment variable or browser code.
- Campus values are normalized and seeded as four rows.
- Profiles use the Auth user ID as their primary key and do not expose student emails.
- Skills and interests use normalized many-to-many tables.
- Follows are immediate, directional, cannot target the same user, and use a composite primary key to prevent duplicates. Existing accepted connections are migrated to mutual follows.
- Social posts are chronological and support one private image, video, or document attachment up to 25 MB. Feed links use short-lived signed URLs; indexed likes and comments provide accurate engagement counts.
- Direct and group messages use client-side XChaCha20-Poly1305 encryption, per-device sealed key envelopes for every conversation member, Ed25519 signatures, member-only RLS, per-user read state, and Supabase Realtime delivery. See
docs/e2ee-direct-messages.md. - The notification center covers new followers, followed-user posts and collaborations, likes, comments, group invitations, and collaboration-passport confirmations.
- Collaboration posts are chronological, campus-scoped or NST-wide, and owned by their authors.
- RLS limits networking data to email-confirmed, manually approved profiles; likes, comments, follows, posts, and uploads are restricted to the authenticated owner where appropriate.
- Avatar and post uploads are limited to authenticated users' own folders and explicit file types. Avatars allow 3 MB; post attachments allow 25 MB.
The optional seed script can create 10–1,000 users plus posts, follows, collaborations, conversations, and messages. It requires a server-only service-role key and an explicit confirmation flag, refuses NODE_ENV=production, and refuses remote projects unless separately confirmed.
PEERGRID_SEED_CONFIRM=yes \
PEERGRID_SEED_USERS=250 \
PEERGRID_SEED_EMAIL_DOMAIN=your-allowed-development-domain.test \
SUPABASE_SERVICE_ROLE_KEY=your-server-only-key \
npm run seed:devUse a disposable local or staging Supabase project. For a remote staging project, also set PEERGRID_ALLOW_REMOTE_SEED=yes. Never expose the service-role key through a NEXT_PUBLIC_ variable.
- Set a stable
NEXT_SERVER_ACTIONS_ENCRYPTION_KEYacross all production instances. - Keep the service-role key out of the Next.js client and normal web runtime.
- Apply all migrations before deploying the matching application build.
- Migration
20260901000000_e2ee_collaboration_activity_notifications.sqlpermanently removes legacy plaintext DM rows (conversation shells remain), then enables ciphertext-only DMs. Back up only if you intentionally need an offline compliance archive before applying it. - Apply
20260901010000_groups_and_social_notifications.sqlimmediately afterward to enable group membership, per-member unread state, general social notifications, and the notification dropdown. - Enable Realtime for
messages,collaboration_activity_events, andnotifications; the migration adds them tosupabase_realtimewhen they are not already present. - Configure Supabase Auth redirect URLs for the production origin and
/auth/callback. - The app bounds feed, profile, collaboration, discovery, conversation, comment, follower, and message reads; messages load the latest page first and fetch older history on demand.
npm run lint
npm run build