Inspiration
Schools run on tools they don't control. Staff coordinate over personal WhatsApp groups and personal Gmail accounts — mixing work with private life, leaking student data, and letting anyone who finds the link into a "staff" group. There's no verification, no 2FA, and no audit trail. We built the School Staff Platform so a school can own a sealed, internal communication network: accounts start locked (pending), only an admin can verify a member. It's the staff intranet schools never had time to build.
What it does
A full-stack internal staff platform: (1) internal email — inbox / sent / threaded compose-reply, self-contained; (2) messaging — 1:1 and group threads with unread counts, edit/delete, and auto-created group chats; (3) groups — create/join/leave with owner/admin management; (4) a documents drive — public & private, base64 upload, safe inline rendering; (5) a posts feed with likes and Reddit-style nested comments, plus events (chat + members + attached docs) and Substack-style blogs any user can publish; (6) a user directory with profiles and avatars; and (7) a full admin console — verify pending members, change roles, lock/unlock the portal, a live activity log, and broadcast announcements.Security is built in- rate-limited login, and session-fixation-safe cookies.
How we built it
Node.js + Express backend (express-session, helmet, zod, bcrypt). A single provider-agnostic async DB interface (db.get / db.all / db.run / db.insert / db.transaction) lets the same SQL run on local SQLite (better-sqlite3) and production Supabase Postgres (pg) — routes use the Postgres $1,$2 dialect and the SQLite adapter rewrites placeholders, so there are zero dialect forks in route code. The frontend is a React 18 + Vite SPA. It deploys to Vercel as a serverless function (serverless-http wraps the Express app; api/index.js is the entry) with the SPA served statically and /api/* rewritten to the function; Supabase holds Postgres plus the session store.
Challenges we ran into
- One SQL, two engines: keeping transactions safe while rewriting $N → ? between SQLite and Postgres.
- Vercel's serverless body cap (~4.5 MB): base64 uploads inflate ~37%, so the real max file is 3 MB — we encoded that as a single source of truth (config.maxUploadMb / maxUploadBytes / vercelBodyCapMb) driving the scoped body parsers.
- SPA + API on one serverless deploy: getting the index.html fallback and /api rewrite right so the root URL doesn't 404.
- Verify-only onboarding on a fresh Postgres DB: signup only creates pending accounts and seed.js is SQLite-only, so the first admin is promoted via BOOTSTRAP_ADMIN_EMAIL.
Accomplishments that we're proud of
- A genuinely sealed system: pending-by-default, constant-time backup-code comparison, SVG upload rejection + nosniff.
- One codebase, two databases, no dialect forks in route code.
- A production-secret guard that refuses to boot with dev secrets or a missing DATABASE_URL.
What we learned
Object storage for production-scale files (past the 3 MB cap); SSO/SAML for district-wide rollout; per-department spaces and richer audit exports; and a one-click deploy template (env + Supabase migration wired).
What's next for Complete School Staff Platform
Object storage for production-scale files (past the 3 MB cap); SSO/SAML for district-wide rollout; per-department spaces and richer audit exports; and a one-click deploy template (env + Supabase migration wired).
Credentials for admin
email- [email protected] password- asdasdasd (the password does not apply to the gmail or any accounts.) When running locally, credentials are given in the repo's readme.
Discord Username
VolarQuill
Built With
- bcrypt
- express.js
- javascript
- node.js
- postgresql
- react
- sqlite
- supabase
- vercel
- vite
- zod


Log in or sign up for Devpost to join the conversation.