Overview
L.A. Noire is a full-stack investigation platform built for the Web Programming course at Sharif University of Technology, styled after the Rockstar game it takes its name from. The brief asked for a CRUD application; the goal was production architecture for a real domain: cases, evidence, complaints, crime scenes, witness testimony, tip rewards, and a visual detective board.
Built as a team of three. My share was auth and payment on the Django side, and on the React side the payment flow, detective board, high-priority wanted suspects, reward claim UI, plus frontend tests.
Access control as a first-class concern
The core design problem was role-based access control. It is easy to hide a button in the UI and call that access control — and just as easy to ship a hole behind it. Here RBAC is enforced in three places that have to agree: route guards on the frontend, conditional rendering of UI actions, and permission checks on every Django REST endpoint.
The important part is the asymmetry in how those three are treated. The frontend checks exist for user experience: they keep people from walking into a door that will not open. The API checks are the actual boundary. A request that bypasses the client entirely still has to satisfy the same policy, because the policy lives on the server and the client only mirrors it.
Frontend
React with Vite, using TanStack Query for server state and Zustand for the client state that genuinely is client state — a distinction worth being strict about, since conflating the two is what turns a state layer into a second, worse cache.
Forms run through React Hook Form with Zod resolvers, so validation rules are
declared once as schemas and reused for both the form and the typed API
boundary. React Flow (@xyflow/react) drives the detective board, where
relationships between cases, evidence, and suspects are explored as a graph
rather than nested tables.
Backend
Django and Django REST Framework over PostgreSQL, with JWT authentication via
PyJWT, django-filter for composable query parameters, and Gunicorn as the
application server. Permissions are attached at the viewset level so a new
endpoint is access-controlled by default rather than by remembering to add a
check.
Delivery
GitHub Actions runs build, test, and deploy on every push. The whole stack is containerized, so the environment that runs in development is the environment that runs in deployment — which removes the class of bug where something works locally and nowhere else.




















