Anonymous, real-time exam sentiment voting for University of Waterloo students.
Users can search exams, create new ones with duplicate detection, and vote on whether an exam was TOUCHING or TOUCHY. Tallies are stored in D1 and pushed live over Durable Object WebSockets.
- Frontend: React 19, TypeScript, Vite, Tailwind CSS
- Backend: Cloudflare Workers with Hono
- Data: Cloudflare D1
- Real-time: Cloudflare Durable Objects + WebSockets
- Tooling: bun, Wrangler
- Search recent or matching exams
- Create new exam entries by faculty, course number, term, and exam name
- Duplicate detection before exam creation
- One vote per browser per exam, with vote updates allowed
- Live tally updates on exam pages
- Anonymous vote tracking via signed httpOnly cookie + hashed device ID
src/client/ React app
src/worker/ Cloudflare Worker, API routes, Durable Object logic
migrations/ D1 schema
public/ Static assets
wrangler.toml Worker, D1, Durable Object, and asset bindings
bun run dev- start the Vite frontend on port5173bun run build- build the frontend intodist/bun run check- run TypeScript type-checkingbun run worker:types- regenerate Wrangler environment typesbun run deploy- deploy the Worker and static assets with Wrangler
- Install dependencies:
bun install- Type-check the project:
bun run check- Build the frontend bundle:
bun run build- The app expects a Cloudflare D1 database bound as
DB. - The Worker expects a
COOKIE_SECRETsecret binding. - The Durable Object binding is
EXAM_ROOMS. - The SQL schema lives in migrations/0001_initial.sql.
bun run devonly starts the Vite frontend. This repo does not currently include a Vite proxy or a combined local full-stack script, so/api/*and WebSocket features are not fully wired for standalone local frontend dev.
src/client/main.tsxmounts aHashRouterapp with three routes:/recent exams and search/createexam creation flow/exam/:idlive results and voting
src/worker/index.tsserves the API under/api/*and falls through to static assets for everything else.src/worker/room.tskeeps the latest tally snapshot per exam room and broadcasts updates to connected WebSocket clients.- Votes are stored in
votes, whileexam_statsmaintains the aggregate counters used by the UI.
Defined in migrations/0001_initial.sql:
coursestermsexamsvotesexam_stats
- Worker entrypoint: src/worker/index.ts
- Static asset directory:
dist - Wrangler config: wrangler.toml
- Before deploy, make sure the D1 database exists, the schema has been applied, and
COOKIE_SECREThas been set in Wrangler.
Implemented now:
- exam creation
- duplicate checking
- search and recent listings
- anonymous voting
- live tally updates