Role-based cricket video platform: analysts manage matches and upload clips to S3
Problem
Cricket analysts need a structured way to organize tournament/match video footage per player and share it with players, instead of ad-hoc file sharing (README is boilerplate create-next-app text; problem inferred from code).
What it does
A Next.js app with three roles (admin, video_analyst, player). Admins manage users (create/edit/delete, app/api/admin/*), analysts create tournaments and matches, assign players, and batch-upload videos via S3 presigned URLs; players get a dashboard of their tournaments/matches/videos. JWT httpOnly-cookie auth with role-based dashboard routing in proxy.ts.
Architecture
[web] Next.js 16 App Router UI (login + 3 role dashboards) (client) [api] Next.js API routes (auth, admin, analyst, videos) (api) [db] PostgreSQL (Neon) via pg Pool (store) [s3] AWS S3 video bucket (store) [auth] JWT cookie auth + role middleware (proxy.ts) (worker) web ──▶ api · fetch JSON (login, CRUD, presign, save) api ──▶ auth · verify JWT from 'token' cookie api ──▶ db · SQL via pg pool (users, tournaments, matches, match_players, players, videos) api ──▶ s3 · generate presigned PUT URLs (15 min), create folder placeholders web ──▶ s3 · direct PUT upload with XHR retry/backoff (lib/upload-videos.ts) web ──▶ api · /api/videos/save records successful uploads in DB
Simplified — box-and-arrow sketch of the repo structure, not a system diagram.
Decisions
01
Direct-to-S3 uploads via presigned URLs
Large video files bypass the server; presign route validates analyst ownership, match membership, and builds sanitized folder keys (player/tournament/match) before signing
Evidence: app/api/videos/presigned-urls/route.ts; lib/upload-videos.ts
02
Client-side upload orchestration with retry and partial-failure handling
uploadWithRetry does 3 attempts with exponential backoff and Promise.allSettled so individual video failures don't abort the batch; only succeeded files are saved to DB
Evidence: lib/upload-videos.ts
03
JWT cookie with role-based dashboard routing
Single proxy() rewrites /dashboard/* to the role-specific dashboard and redirects unauthorized roles to /restricted
Evidence: proxy.ts
04
Zod schemas discriminated by gender with separate category enums
Men categories (under_14..ranji) differ from women (under_16..senior); reused across player and tournament creation
Evidence: types/zod.ts
05
Separate presign then save steps
DB rows for videos are only written after the client confirms the S3 PUT succeeded, avoiding orphaned records for failed uploads
Evidence: app/api/videos/save/route.ts; lib/upload-videos.ts
The hard parts
- Authorization matrix in the presign route: analyst must own the tournament, players must be match members, tournament/match mappings must be consistent, and adhoc vs match uploads can't be mixed (app/api/videos/presigned-urls/route.ts:126-296)
- S3 key layout with sanitized player/tournament/match folder segments plus zero-byte 'folder placeholder' objects created on demand (app/api/videos/presigned-urls/route.ts:334-388)
- Batch upload resilience: per-file retry with backoff, progress reporting, and partial-batch save semantics (lib/upload-videos.ts:24-160)
Known limitations
- Player backend is unfinished: app/api/player/[userId]/dashboard/route.ts and app/api/player/videos/[videoId]/play/route.ts are empty 0-byte files, so the player dashboard and video playback API have no implementation
- Several analyst routes (create_match, create_tournament, delete_*) perform no JWT/role check on the request, unlike the videos routes (inferred from app/api/video_analyst/create_match/route.ts)
- JWT falls back to the hardcoded string 'fallback_secret' when JWT_SECRET is unset (proxy.ts:14, app/api/login/route.ts:66)
- No video analysis itself — despite the name, the app manages and stores footage but contains no analytics/ML code
- No tests, no CI, no Docker, no env example file