A peer-to-peer learning and collaboration platform for university students, built from the SRS - with a few deliberate deviations from it, explained below.
- Backend: Node.js (Express) + PostgreSQL (via Prisma, hosted on Supabase) + Socket.io
- Frontend: React.js (Vite)
- Auth: institutional-email-gated registration, real email verification via Resend, bcrypt (cost 12), JWT sessions, RBAC
- Real-time: WebSocket (Socket.io) for direct messaging, per SRS 3.4
- File uploads: 50MB limit, PDF/DOCX/PNG/JPEG only, per NFR5 - stored directly in Postgres, not S3
This was built and verified against a real running PostgreSQL instance
at every stage - registration, tutor search, session booking, ratings,
study groups, messaging, recurring availability, ICS calendar files, and
resource uploads were all run end-to-end (several via a real HTTP round
trip through the actual route/multer code, not just raw SQL). The one
thing that couldn't be verified in the dev sandbox is prisma generate
itself and a live Resend send, since both need external hosts that were
network-blocked there - that's a normal one-time step you'll run locally
with real internet access.
The SRS specifies Railway, Google Calendar, SendGrid, Firebase, and AWS S3. This build uses different, deliberately-chosen alternatives instead - here's what changed and why.
No functional difference to PeerLink itself; Render was simply the platform
actually used to deploy this build. See DEPLOYMENT.md for Render-specific
steps (root directory, build/start commands, environment variables).
Google Calendar's API needs an OAuth consent flow - a registered Google Cloud project, a verified app, per-user authorization. That's real infrastructure with nothing to do with PeerLink's own functionality, and it doesn't demo well for a student project (a judge would need to actually grant OAuth access to a stranger's app).
The practical alternative: GET /sessions/:id/calendar.ics generates a
real file in the iCalendar standard - the same format Google Calendar,
Outlook, and Apple Calendar all import natively. No API key, no OAuth, no
third-party account, and the session still ends up in whichever calendar
app the user actually uses. This was verified by parsing the generated file
with an independent third-party ICS parser (not just visual inspection),
confirming it's genuinely spec-compliant.
On top of that, per your instruction to add a built-in calendar as a
fallback: there's also a /calendar page in the app itself - a month
grid showing every upcoming and past session, no download required.
Functionally a straight swap - same idea (transactional email API), same
graceful-degradation pattern as your faithhoopers project's email setup
(no API key → logs to console and hands back the link directly, instead of
failing registration). backend/src/lib/email.js uses Resend's HTTP API
directly via Node's built-in fetch, so there's no extra dependency to
install. Sign up free at resend.com, verify a sending
domain (or use their shared test domain for a demo), and set
RESEND_API_KEY + EMAIL_FROM.
Also added while wiring this up: a POST /auth/verify/resend endpoint and
matching UI on the Login page, so a lost or expired verification email
isn't a dead end.
backend/src/lib/storage.js uses multer's in-memory storage, and
routes/resources.js writes the raw file bytes straight into a Postgres
Bytes column on Resource - nothing touches local disk or any external
service. This was verified with a real HTTP upload → Postgres → download
round trip, confirming the downloaded bytes match the original exactly.
Notifications (FR9) were already Postgres-only and didn't need to change -
push.js now says so explicitly rather than describing itself as a
placeholder for Firebase Cloud Messaging, since per your instructions,
Firebase isn't part of this build's design at all.
schema.prisma's datasource block now uses Prisma's directUrl field -
the officially recommended fix for the exact PgBouncer issue flagged in
the team stand-up. The app runs on the pooled connection (DATABASE_URL,
port 6543) for normal queries; prisma migrate/db push automatically use
the direct, non-pooled connection (DIRECT_URL, port 5432) instead - no
more manually swapping connection strings for migrations.
- Notifications inbox - a real page at
/notifications, not just an unread count. Click a notification to mark it read, or "mark all as read." The nav bar shows a live unread count. - Recurring availability - a tutor can add a slot "just once" or repeat
it weekly for 4/8/12 weeks in one action, all sharing a
recurrenceGroupId. "Remove series" cancels every future slot in that series at once. - Session location field - sessions can carry a Zoom link or physical location, settable at booking time or edited afterward by either party from the Sessions page.
- Course autocomplete on Resources - upload and filter now use a live
datalistof every course code already in use, instead of free-typing a code from scratch each time. - "Add to calendar" - a real
.icsdownload button on every confirmed session, plus the built-in/calendarmonth view described above. - Resend-verification flow - if a verification email expires or gets lost, the Login page offers to send a fresh one.
- Admin panel (
/admin, ADMIN role only) - platform-wide stats, a tutor-verification queue, and basic resource moderation. Fulfills the SRS's Platform Administrator user class, which previously had no UI at all. Tutor verification is now genuinely load-bearing, not cosmetic:isTutoris self-declared at registration, but a new tutor'stutorVerifiedflag staysfalseuntil an admin approves them, and unverified tutors don't appear in student search until they are. Their own profile page still resolves via direct link, with a "pending admin verification" notice instead of hiding entirely.
Go to supabase.com → New Project. Once it's provisioned, Project Settings → Database → Connection string gives you two URLs you need:
- Pooled (port 6543,
?pgbouncer=true) →DATABASE_URL - Direct (port 5432) →
DIRECT_URL
cd backend
cp .env.example .env # paste DATABASE_URL, DIRECT_URL, set a JWT_SECRET
npm install
npx prisma migrate dev --name init
npm run seed # creates a demo tutor + student + session + group
npm run dev # http://localhost:4000cd frontend
cp .env.example .env
npm install
npm run dev # http://localhost:5173- Tutor (verified):
grace.uwase@alueducation.com/Password123! - Tutor (pending admin approval):
peter.ndayishimiye@alueducation.com/Password123! - Student:
jean.mugisha@alueducation.com/Password123! - Admin:
admin@alueducation.com/Password123!
Registration requires an @alueducation.com email by default - change
INSTITUTIONAL_EMAIL_DOMAIN in backend/.env if your cohort uses a
different domain. Without RESEND_API_KEY set, verification/reset links
are returned directly in the API response instead of emailed - fine for
local dev and grading, real email needs the key configured.
- Register a new student account live (shows FR1 + real email verification - or the on-screen dev link if Resend isn't configured).
- Log in as the seeded tutor, add a recurring weekly availability slot.
- Log in as the seeded student, search tutors by subject, open the tutor's profile, book a slot with a Zoom link as the location (FR3, FR4).
- Download the session's "Add to calendar"
.icsfile, and/or show the built-in/calendarview. - Switch back to the tutor, mark the session completed.
- Switch to the student, leave a rating (FR8) - watch the tutor's average update live.
- Open Messages in two browser windows (or one normal + one incognito) logged in as each user, send a message, and show it arriving instantly via Socket.io (FR6, NFR3).
- Upload a resource, show course autocomplete, and download it back.
- Create a study group and have the other account request to join (FR7).
- Open Notifications and show the unread badge clearing.
- Log in as admin (
admin@alueducation.com), open/admin, and approve Peter's pending tutor verification live - then switch back to the student account and show him now appearing in search.
- Group messaging (FR6.1) reuses the same
Messagemodel but isn't wired into the UI yet - direct messaging is fully working. - Mobile app (React Native, per SRS 2.4) isn't started - this is the web client only.
- If push notifications (not just in-app) are ever wanted, Web Push (browser-native, no vendor account needed) is a lighter next step than Firebase Cloud Messaging - a genuinely separate feature, not a loose end of the current design.
A few ideas worth considering for a future iteration, roughly in order of effort vs. payoff:
- Smart tutor matching - suggest tutors based on a student's own subjects/faculty instead of pure search; a good SQL query dressed up as a feature, not real ML, but demos well.
- Gamification - streaks, completed-session badges, or a top-rated-tutor leaderboard; cheap to build, memorable in a presentation.