A production training-and-certification platform for law-enforcement agencies — a legacy Classic ASP system rebuilt, solo, on a modern security-hardened stack.
Role: Sole developer (contract) · Timeline: ~6 weeks
Note on the source code. This is a case study, not the source repository. LERC is paid contract work, so the application code and the client's course content live in a private repo. Happy to walk through the architecture, the security model, or specific code in an interview, or to grant repo access on request.
LERC delivers an online report-writing certification course used by law-enforcement agencies to train and certify officers (the course has served 35,000+ officers since 1978). The engagement replaced a legacy Classic ASP system with a modern web application — built end-to-end as the sole developer, owning requirements, architecture, security, implementation, testing, and deployment.
The product spans five surfaces: a public marketing/order page, an access-code student course flow (247 pages across 8 chapters with interactive practice quizzes), a graded final-exam path with certificate generation, an admin console, and a department-head portal for tracking officer progress.
Officers enter with a department-issued access code (no accounts), then land on a course home that tracks progress across all eight chapters and resumes exactly where they left off.
| Access-code login | Course home — 8 chapters |
|---|---|
![]() |
![]() |
(Course home shows the chapter list and progress only — the lesson content itself is the client's and stays private.)
Officers don't have individual accounts, agencies share networks, and the exam is a real certification — so the interesting engineering is in the trust model, not CRUD.
Two separate auth systems for two user classes:
- Admins → Supabase Auth + PostgreSQL row-level security (RLS) for role-scoped, department-isolated data.
- Officers → a custom access-code login that mints an HMAC-SHA256-signed session cookie. The signature is the session — no session table, no shared server state — so it stays correct across serverless instances.
The graded exam is hardened against forgery, answer-swapping, and double-submission:
- Questions are served with an HMAC-signed token binding the exact question IDs to that student + chapter.
- On submit: verify the signature → enforce single-use via a DB unique constraint before grading → check submitted IDs match served IDs → never return correct answers on a fail (with instant retakes, leaking them would be an answer oracle).
- Per-user Redis rate limiting (Upstash) on the grading path.
Every contended write is guarded by a real database constraint, not app-level read-then-write:
-- Progress can never regress on backward navigation (monotonic guard)
UPDATE enrollment_progress
SET current_page = $newPage
WHERE user_id = $u AND chapter = $c
AND current_page < $newPage;
-- Exam token replay protection enforced at the database, not in app logic
CREATE UNIQUE INDEX ux_used_quiz_tokens ON used_quiz_tokens (token_id);(Illustrative patterns — generic engineering, not client content.)
Two administrative surfaces: a super-admin console managing every department (access codes, per-department passwords, department-head assignment) and a department-head portal tracking each officer's progress and quiz completion.
Department, officer, and student identifiers are blurred for privacy — the layout and functionality stay intact. The "Test dept" row is test data, left visible.
Super-admin console
Department-head portal
┌─────────────── Next.js 16 (App Router) ───────────────┐
Public ▶ │ Marketing / order page │
Officer ▶ │ Student course flow → Practice quizzes → Final exam │──▶ Certificate
Admin ▶ │ Admin console │
Dept-head ▶ │ Department progress portal │
└───────────────┬───────────────────────┬────────────────┘
Server Actions / RSC HMAC session cookies
│ │
┌───────────────▼─────────────┐ ┌───────▼────────┐
│ Supabase / PostgreSQL │ │ Upstash Redis │
│ 14-table schema · RLS │ │ rate limiting │
│ 50 forward-only migrations │ └────────────────┘
└──────────────────────────────┘
Deployed on Vercel (GitHub CI/CD)
- Content migration pipeline: a Node/TypeScript tool converted 247 pages of legacy ASP markup into
sanitized semantic HTML through a curated
sanitize-htmlallowlist (XSS + UI-redress protection), re-implementing interactive quizzes as React components. - Data layer: a normalized 14-table schema with 50 forward-only migrations and data-integrity constraints enforcing correctness at the database.
Frontend: Next.js 16 (App Router, Server Components & Server Actions), React 19, TypeScript, Tailwind CSS v4, shadcn/ui, React Hook Form, Zod Backend / Data: Supabase / PostgreSQL (row-level security), 50 migrations, Upstash Redis Security: dual auth, HMAC-SHA256 signing, single-use tokens, rate limiting, XSS mitigation, threat modeling Cloud / Infra: Vercel (serverless + CI/CD), Vercel Blob, Resend (email) Testing: Vitest (405 tests), ESLint, TypeScript
- 405 automated tests (auth, grading, concurrency) · ~12,600 lines of TypeScript
- 50 forward-only DB migrations · 14-table schema
- 95 commits / 45 merged PRs · ~6 weeks, solo
- 247 pages / 8 chapters of legacy content migrated
Everything — as the sole developer, I reverse-engineered requirements from an undocumented legacy system, designed the architecture and the security model, built all five surfaces, wrote the test suite, and shipped to production on Vercel with GitHub-based continuous deployment. I ran an adversarial security review on every feature and migration before merge, and treated tests, lint, type-check, and build as non-negotiable release gates.




