SafeStreets is a multi-app road-safety platform built around three pieces:
client/client_user: the citizen-facing Next.js app and PWA for reporting incidents, tracking personal reports, viewing hotspot maps, and managing notifications.client/client_admin: the admin moderation dashboard for reviewing reports, managing users, and monitoring analytics.server: the FastAPI backend that handles authentication, report ingestion, AI detection orchestration, geocoding, notifications, and admin workflows.
The current codebase supports both AI-assisted traffic violation reporting and a community garbage-reporting mode that skips AI and stores the submission directly.
- Email/password signup, login, forgot-password, and OTP-based reset flow
- Report creation with camera or gallery input
- Traffic report mode with AI detection
- Community garbage issue mode without AI detection
- Device geolocation plus reverse-geocoded address preview
- Image compression before upload
- Offline queue backed by IndexedDB
- Pending-reports screen with retry/sync controls
- Personal dashboard with report stats and history
- Public map and heatmap views of reported incidents
- Leaderboard based on user points
- Notification center and profile page
- PWA manifest and production service worker registration
- Capacitor Android project for mobile packaging
- Separate admin signup and login flow
- Dashboard with totals, recent activity, and charts
- Violation moderation with list and map views
- Single and bulk status updates
- Admin review comments stored in report details
- Bulk delete and per-report delete actions
- User list, user detail page, ban/unban, and delete user actions
- JWT-based auth with refresh-token storage
- Role-based access for users vs admins
- Structured logging with request correlation IDs
- Rate limiting on signup, login, password reset, and report submission
- Supabase-backed data access and storage uploads
- LocationIQ reverse geocoding with in-memory caching and rate limiting
- Primary AI detector plus optional Anthropic Claude parallel ensemble
- Resend email alerts for newly reported violations
- Notification creation on admin verification/rejection
- Lightweight
GET /violations/countendpoint for profile stats
.
├── client/
│ ├── client_admin/
│ │ ├── app/ # Admin auth + dashboard routes
│ │ ├── components/ # Charts, maps, UI primitives
│ │ ├── services/api.ts # Axios client with admin token handling
│ │ └── package.json
│ └── client_user/
│ ├── app/ # User routes (auth, dashboard, report, map, etc.)
│ ├── components/ # Navbar, map, notification bell, tables, UI
│ ├── public/ # PWA manifest, icons, service worker
│ ├── services/ # API client, offline queue/sync, image compression
│ ├── android/ # Capacitor Android project
│ ├── capacitor.config.json
│ └── package.json
├── server/
│ ├── core/ # Config, auth helpers, dependencies, limiter
│ ├── routers/ # FastAPI route modules
│ ├── services/ # Detector + email integrations
│ ├── utils/ # Supabase, logging, geocoding helpers
│ ├── scripts/ # Maintenance utilities
│ ├── main.py # FastAPI app entrypoint
│ └── requirements.txt
└── venv/ # Local Python virtual environment (if created)
- A user signs in through the user app and receives a JWT access token plus a stored refresh token on the backend.
- Report submission sends 1 to 3 images plus location and metadata to
POST /violations/. - For traffic reports, the backend calls the primary detector and can optionally run Claude in parallel before merging results.
- The backend reverse-geocodes the coordinates, uploads evidence images to the Supabase Storage bucket
violation-evidence, and inserts the report into theviolationstable. - Admins review reports in the admin app. Verification awards points and creates a notification for the user; rejection also creates a notification.
- The user app reads dashboard stats, map data, notifications, leaderboard data, and profile info from the FastAPI backend.
| Layer | Implementation |
|---|---|
| User frontend | Next.js 15, React 19, TypeScript, Tailwind CSS v4, Framer Motion, Axios, Leaflet |
| Admin frontend | Next.js 15, React 19, TypeScript, Tailwind CSS v4, Recharts, Leaflet |
| Mobile shell | Capacitor 8 with Android project checked in |
| Backend | FastAPI, Uvicorn, Pydantic, SlowAPI |
| Auth and data | Supabase tables + storage |
| Password hashing | Argon2 |
| Geocoding | LocationIQ reverse geocoding |
| AI integrations | External model API plus optional Anthropic Claude |
| Email alerts | Resend |
- Node.js 18+ and npm
- Python 3.12+
- A Supabase project with the expected tables and storage bucket
- A reachable AI inference endpoint for traffic-report analysis
- A valid
JWT_SECRET - Optional but strongly recommended external services:
- LocationIQ for address lookup
- Anthropic for parallel Claude validation
- Resend for alert emails
The backend reads environment variables through server/core/config.py. JWT_SECRET is mandatory; the server refuses to boot without it.
| Variable | Required | Purpose |
|---|---|---|
SUPABASE_URL |
Yes | Supabase project URL |
SUPABASE_ANON_KEY |
Yes | Fallback key if no service-role key is set |
SUPABASE_SERVICE_ROLE_KEY |
Recommended | Preferred server-side key for unrestricted backend operations |
JWT_SECRET |
Yes | Secret used to sign and verify access tokens |
JWT_ALGO |
No | JWT algorithm, defaults to HS256 |
ACCESS_TOKEN_EXPIRE_MINUTES |
No | Access-token TTL, defaults to 15 |
REFRESH_TOKEN_EXPIRE_DAYS |
No | Refresh-token TTL, defaults to 30 |
ALLOWED_ORIGINS |
No | Comma-separated CORS allowlist, defaults to * |
DEBUG |
No | Enables verbose error responses when truthy |
MODEL_API_URL |
Required for traffic AI | External inference endpoint used by server/services/detector.py |
CLAUDE_PARALLEL_ENABLED |
No | Enables parallel Claude validation when truthy |
ANTHROPIC_API_KEY |
Required only if Claude is enabled | Anthropic API key |
ANTHROPIC_API_URL |
No | Anthropic messages endpoint override |
CLAUDE_MODEL |
No | Claude model name |
CLAUDE_TIMEOUT_SECONDS |
No | Timeout for Claude requests |
CLAUDE_MAX_TOKENS |
No | Max Claude output tokens |
LOCATIONIQ_API_KEY |
Recommended | Reverse geocoding API key |
RESEND_API_KEY |
Optional | Enables email alerts |
RESEND_FROM_EMAIL |
Optional | Sender address for alert emails |
RESEND_REPLY_TO |
Optional | Reply-to address for alert emails |
RESEND_TIMEOUT_SECONDS |
No | Timeout for Resend requests |
ALERT_RECIPIENT_EMAIL |
Optional | Destination email for new-report alerts |
SMTP_HOST |
No | Present in config but not currently used by the active email path |
SMTP_PORT |
No | Present in config but not currently used by the active email path |
SMTP_USER |
No | Present in config but not currently used by the active email path |
SMTP_PASSWORD |
No | Present in config but not currently used by the active email path |
SMTP_TLS_MODE |
No | Present in config but not currently used by the active email path |
SMTP_TIMEOUT_SECONDS |
No | Present in config but not currently used by the active email path |
Example backend .env:
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
JWT_SECRET=replace-with-a-long-random-secret
JWT_ALGO=HS256
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3001
MODEL_API_URL=https://your-model-service.example.com/detect
LOCATIONIQ_API_KEY=your-locationiq-key
CLAUDE_PARALLEL_ENABLED=false
ANTHROPIC_API_KEY=
RESEND_API_KEY=
RESEND_FROM_EMAIL=
ALERT_RECIPIENT_EMAIL=Both frontends use NEXT_PUBLIC_API_URL.
| App | Variable | Default in code |
|---|---|---|
client/client_user |
NEXT_PUBLIC_API_URL |
https://safe-streets-backend.onrender.com |
client/client_admin |
NEXT_PUBLIC_API_URL |
http://localhost:8000 |
Set this explicitly in each app's .env.local during development so both frontends point at the same backend.
Example:
NEXT_PUBLIC_API_URL=http://localhost:8000python3 -m venv venv
source venv/bin/activate
pip install -r server/requirements.txt
cd server
uvicorn main:app --reload --port 8000The backend serves:
http://localhost:8000/http://localhost:8000/health/
cd client/client_user
npm install
npm run dev -- --port 3000If you want both Next.js apps running at the same time, start the admin app on a different port:
cd client/client_admin
npm install
npm run dev -- --port 3001The user app is configured as a static-exported PWA:
client/client_user/next.config.tsusesoutput: 'export'client/client_user/public/manifest.jsondefines install metadataclient/client_user/components/ServiceWorkerRegistration.tsxregisters the service worker only in productionclient/client_user/public/sw.jscaches the app shell and listens for background sync eventsclient/client_user/capacitor.config.jsonpoints Capacitor atout/
cd client/client_user
npm install
npm run build:mobileThat runs next build && npx cap sync, which updates the checked-in Android project under client/client_user/android/.
The Android project already includes:
INTERNETACCESS_COARSE_LOCATIONACCESS_FINE_LOCATION
Capacitor plugins in use:
@capacitor/camera@capacitor/geolocation
Offline support is implemented entirely in the user app:
- Pending reports are stored in IndexedDB by
client/client_user/services/offlineQueue.ts - Sync and retry logic lives in
client/client_user/services/offlineSync.ts - The pending queue UI lives at
/pending-reports - The report form compresses large or non-JPEG images before storing or uploading them
- Background sync is requested when supported by the browser
Important deployment note:
- Foreground sync uses the configured Axios
NEXT_PUBLIC_API_URL - The production service worker currently posts background-sync submissions to
/api/violations/
If you deploy the statically exported user app behind a domain that does not proxy /api/* to FastAPI, service-worker background sync will need a reverse proxy or an implementation adjustment.
The code references these Supabase resources directly:
profilesadminsviolationsnotificationsrefresh_tokensauth_logs
- Bucket:
violation-evidence
increment_points(u_id, amount)
If the RPC is missing, the backend falls back to a fetch-and-update path when awarding points after verification.
| Method | Endpoint | Description |
|---|---|---|
GET |
/ |
Basic API status payload |
GET |
/health/ |
Health-check endpoint |
POST |
/auth/signup |
User signup |
POST |
/auth/login |
User login; returns access and refresh tokens |
POST |
/auth/refresh |
Issues a new access token from a stored refresh token |
POST |
/auth/forgot-password |
Generates reset OTP |
POST |
/auth/reset-password |
Resets password using email + OTP |
GET |
/users/me |
Current user profile |
GET |
/users/leaderboard |
Top users by points |
GET |
/geocode/reverse?lat=...&lon=... |
Reverse-geocodes coordinates |
GET |
/notifications/ |
Current user's notifications |
PUT |
/notifications/{notification_id}/read |
Marks one notification as read |
GET |
/violations/public |
Public hotspot data for the map |
GET |
/violations/ |
Current user's full report list |
GET |
/violations/count |
Current user's total report count |
POST |
/violations/ |
Creates a new report |
DELETE |
/violations/{violation_id} |
Deletes one of the user's own reports |
POST /violations/ expects multipart form data with:
files: 1 to 3 uploaded imageslatitudelongitudetimestampreport_mode:trafficorcommunity_garbage- Optional
user_violation_type - Optional
description - Optional
severity - Optional
vehicle_number
Behavior:
trafficreports go through AI detectioncommunity_garbagereports skip AI and are stored as community issues
| Method | Endpoint | Description |
|---|---|---|
POST |
/admin/auth/signup |
Create an admin account |
POST |
/admin/auth/login |
Log in as admin |
| Method | Endpoint | Description |
|---|---|---|
GET |
/admin/dashboard |
Aggregate admin dashboard stats |
GET |
/admin/violations |
Paginated violation list with optional status filter |
PUT |
/admin/violations/{violation_id}/status |
Verify/reject/update a report and optionally attach admin comments |
PUT |
/admin/violations/bulk-status |
Bulk verify or reject up to 50 reports |
DELETE |
/admin/violations/bulk |
Bulk delete reports |
DELETE |
/admin/violations/{violation_id} |
Hard-delete one report |
GET |
/admin/users |
List user accounts |
GET |
/admin/users/{user_id} |
Fetch one user profile and stats |
GET |
/admin/users/{user_id}/violations |
Fetch one user's reports |
PUT |
/admin/users/{user_id}/ban |
Ban or unban a user |
DELETE |
/admin/users/{user_id} |
Delete a user and related records |
client/client_user/app/scan/page.tsxis an alias of the main report page.- The dedicated admin experience lives in
client/client_admin, butclient/client_user/app/admin/page.tsxstill contains a simple legacy admin screen that also hits admin endpoints. - The user dashboard still fetches the full
/violations/list because it needs report rows for the table and chart; the profile page now uses/violations/countfor the lightweight report-count stat. - The backend's default
MODEL_API_URLpoints to/detect, but the FastAPI server itself does not expose that route. A separate model service is expected. - Email alerts use Resend, not the SMTP settings currently exposed in config.
- The service worker is intentionally disabled in development to avoid stale caches and route artifacts.
cd client/client_user && npm run lint
cd client/client_user && npm run build
cd client/client_user && npm run build:mobile
cd client/client_admin && npm run lint
cd client/client_admin && npm run buildsource venv/bin/activate
cd server
python check_setup.py
python scripts/fix_addresses.pycheck_setup.py currently verifies access to the violations table. fix_addresses.py backfills address details for stored reports.
- If the backend fails on startup, check
JWT_SECRETfirst. - If traffic reports fail but community garbage reports work, verify
MODEL_API_URL. - If addresses do not resolve, verify
LOCATIONIQ_API_KEY. - If admin verification works but no alert email is sent, check
RESEND_API_KEY,RESEND_FROM_EMAIL, andALERT_RECIPIENT_EMAIL. - If the user app and admin app appear to talk to different backends locally, set
NEXT_PUBLIC_API_URLexplicitly in both apps. - If offline sync behaves differently between foreground sync and background sync, verify whether
/api/*is proxied to FastAPI in your deployment.