tpt-police

TypeScript

Open-source national law enforcement management platform — multi-tenant, 4-tier hierarchy (Nation → Province → City → Precinct). Built with Next.js, TypeScript, Prisma, and PostgreSQL. Covers dispatch, case management, evidence room, HR, ERP, body-worn cameras, and a public portal.

0 stars0 forks0 watchersMIT License
case-managementdispatch-systemevidence-managementlaw-enforcementmulti-tenantnextjsopen-sourcepolice-management-systempublic-safetytypescript

Languages

TypeScript99.0%JavaScript0.6%CSS0.4%Dockerfile0.1%
README

TPT Police — National Law Enforcement Management Platform

MIT License Next.js TypeScript Prisma

Multi-tenant national police department management platform serving a 4-tier hierarchy: Nation → Province → City → Precinct.

Built with Next.js 16, TypeScript, PostgreSQL (Prisma ORM), NextAuth.js v5, and shadcn/ui.

Open source under the MIT License. See LICENSE.

Table of Contents


Architecture Overview

┌─────────────────────────────────────────────────────────┐
│                     Load Balancer                        │
├─────────────────────────────────────────────────────────┤
│  Next.js App (Docker / Vercel / Gov Cloud)              │
│  ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐  │
│  │ App Router   │ │ API Routes   │ │ NextAuth.js Auth │  │
│  │ (RSC/SSR)    │ │ (REST)       │ │ (JWT Sessions)   │  │
│  └─────────────┘ └──────────────┘ └──────────────────┘  │
├─────────────────────────────────────────────────────────┤
│  PostgreSQL 16 (Primary DB - Prisma ORM)                │
│  Redis 7 (Session cache, optional rate limiting)        │
├─────────────────────────────────────────────────────────┤
│  Cloudflare R2 / Wasabi (File/Object Storage)           │
│  SMTP (Email notifications, FOIA responses)             │
└─────────────────────────────────────────────────────────┘

Multi-Tenancy Model

The platform supports a 4-tier tenant hierarchy:

  • NATION — Super Admin view, cross-tenant analytics
  • PROVINCE — Province-level admin over cities and precincts
  • CITY — City-level admin over precincts
  • PRECINCT — Individual precinct operations (cases, HR, ERP, dispatch)

All data is scoped by tenantId. Cross-tier read access is granted by role hierarchy (e.g., a Province Admin can view any precinct within that province).


Tech Stack

LayerTechnology
FrameworkNext.js 16 (App Router, RSC + Server Actions)
LanguageTypeScript 5
UIshadcn/ui + Tailwind CSS v4 + Sonner (toasts)
AuthNextAuth.js v5 (beta) — Credentials + JWT + MFA (TOTP)
ORMPrisma 6 + PostgreSQL 16
ValidationZod 4 — all API routes and forms
StorageCloudflare R2 + Wasabi (abstracted via StorageService)
CacheRedis 7
ContainerDocker — multi-stage build (node:20-alpine)
CI/CDGitHub Actions (lint → type-check → build → docker)

Getting Started (Development)

Prerequisites

  • Node.js 20+
  • Docker Desktop (for PostgreSQL + Redis)
  • npm

Local Setup

# 1. Clone the repository
git clone https://github.com/your-org/tpt-police.git
cd tpt-police

# 2. Copy environment variables
cp .env.example .env.local

# 3. Start PostgreSQL and Redis
docker-compose up -d db redis

# 4. Install dependencies
npm install

# 5. Run database migrations
npx prisma migrate dev

# 6. (Optional) Seed the database with demo data
npx prisma db seed

# 7. Start development server
npm run dev

The app will be available at http://localhost:3000.

Development Commands

CommandDescription
npm run devStart development server
npm run buildProduction build
npm run startStart production server
npm run lintRun ESLint
npx prisma studioOpen Prisma Studio (DB GUI)
npx prisma migrate devCreate and apply migrations
npx prisma generateRegenerate Prisma client

Deployment Guide

Prerequisites

Before deploying, ensure you have:

  1. PostgreSQL 16 database instance (RDS, Cloud SQL, self-hosted, etc.)
  2. Redis 7 instance (ElastiCache, Memorystore, self-hosted, etc.)
  3. Object Storage — Cloudflare R2 and/or Wasabi account for file uploads
  4. SMTP Service — For email notifications (FOIA responses, alerts, etc.)
  5. A domain name with SSL/TLS certificate

Environment Variables

All required environment variables are documented in .env.example. Copy it to .env.local and fill in your values.

VariableRequiredDescription
DATABASE_URLPostgreSQL connection string
NEXTAUTH_URLPublic URL of the app (no trailing slash)
NEXTAUTH_SECRETRandom 32+ char secret for JWT signing
STORAGE_PROVIDERr2 or wasabi
R2_* / WASABI_*Object storage credentials
DISPATCH_API_URLoptionalExternal CAD system endpoint
DISPATCH_API_KEYoptionalCAD API key
DISPATCH_WEBHOOK_SECREToptionalInbound webhook validation
RESEND_API_KEYoptionalEmail via Resend (primary)
MAILJET_API_KEY + MAILJET_SECRET_KEYoptionalEmail via Mailjet (fallback)
REDIS_URLoptionalRequired for multi-instance rate limiting
OLLAMA_BASE_URLoptionalLocal Ollama AI endpoint (for AI features)
OPENROUTER_API_KEYoptionalOpenRouter AI key (alternative to Ollama)
ANPR_API_URL + ANPR_API_KEYoptionalNumber plate lookup API
CLAMD_HOSToptionalClamAV host for malware scanning
LOG_LEVELoptionaldebug/info/warn/error (default: info)

AI features (FOIA drafting, shift briefs) work with either Ollama or OpenRouter. Leave both unset to disable AI entirely.

Docker Deployment (Self-Hosted)

This is the recommended deployment method for on-premises or government cloud environments.

Production Build & Run

# Build and start all services
docker-compose up -d --build

# Or build just the app image separately
docker build -t tpt-police:latest .

docker-compose.yml

The included docker-compose.yml provisions:

  • PostgreSQL 16 — primary database (persistent volume)
  • Redis 7 — session cache and rate limiting
  • App — the Next.js application

For production, replace the .env file values with your production credentials.

Manual Docker Run

docker build -t tpt-police:latest .
docker run -d \
  --name tpt-police \
  -p 3000:3000 \
  --env-file .env.production \
  --restart unless-stopped \
  tpt-police:latest

Database Migrations on Docker

For first-time deployment, run migrations inside the container:

docker exec tpt-police npx prisma migrate deploy

For subsequent releases, migrations can be run as part of the CI/CD pipeline before the new container starts.

Production Architecture (Docker)

┌─────────────────┐
│   Load Balancer  │  (NGINX / Traefik / Cloud LB)
└────────┬─────────┘
         │
┌────────▼─────────┐     ┌──────────────────┐
│  Next.js (App)   │────▶│  PostgreSQL 16    │
│  :3000            │     │  (persistent vol) │
└────────┬─────────┘     └──────────────────┘
         │
┌────────▼─────────┐
│  Redis 7          │
│  (cache/sessions) │
└──────────────────┘

For high availability, run multiple app containers behind a load balancer.


Vercel Deployment

Vercel is the simplest deployment option for the app layer. You'll still need external PostgreSQL and Redis instances.

Steps

  1. Push your repository to GitHub/GitLab/Bitbucket
  2. Import the project in Vercel dashboard
  3. Configure environment variables in Vercel project settings:
    • Add all variables from .env.example
    • Ensure NEXTAUTH_URL is your production domain
  4. Deploy — Vercel will auto-detect Next.js and build

Note: Vercel does not support WebSocket connections natively (they use Serverless Functions). The dispatch live dashboard uses polling, which works fine. If you need real-time WebSockets in the future, consider using a separate WebSocket service or switching to Docker deployment.

Vercel + External Services

  • PostgreSQL: Use RDS, Cloud SQL, or Supabase
  • Redis: Use Upstash (serverless Redis) or ElastiCache
  • Storage: R2 / Wasabi remain as-is, accessed via fetch from serverless functions
  • SMTP: Any SMTP provider (SendGrid, SES, Mailgun, etc.)

Government Cloud / Private Cloud Deployment

For government or private cloud environments (e.g., AWS GovCloud, Azure Government, private data center):

Option A: Docker on VM

# Provision a VM (e.g., AWS EC2, Azure VM, OpenStack)
# Install Docker and Docker Compose
# Copy your .env.production file
# Run:
docker-compose up -d --build

Option B: Kubernetes (EKS, AKS, GKE, OpenShift)

An example Kubernetes deployment manifests directory should be created at k8s/. Key considerations:

  • Use Secrets for environment variables (not ConfigMaps)
  • Use PersistentVolumeClaims for PostgreSQL data
  • Configure HorizontalPodAutoscaler for the app
  • Use Ingress with TLS termination
  • Set up NetworkPolicies for security

Security Considerations for Government Cloud

  1. All traffic must be encrypted in transit — enforce HTTPS at the load balancer level
  2. Database encryption at rest — use encrypted RDS/Cloud SQL instances
  3. Secrets management — use Azure Key Vault, AWS Secrets Manager, or HashiCorp Vault
  4. Audit logging — all application mutations are logged in the audit_logs table (immutable)
  5. Network segmentation — place the app, DB, and Redis in private subnets
  6. Regular backups — configure automated PostgreSQL backups

Environment Configuration (Environments)

For a robust deployment pipeline, maintain separate environment configurations:

EnvironmentPurposePostgreSQLRedisDomain
devLocal developmentDocker ComposeDocker Composelocalhost:3000
stagingPre-production testingRDS/Cloud SQLElastiCache/Memorystorestaging.tptpolice.gov
prodProductionRDS/Cloud SQL (multi-AZ)ElastiCache/Memorystore (replicated)tptpolice.gov
gov-cloudGovernment cloudGovCloud RDSGovCloud ElastiCachetptpolice.gov.internal

Use .env.production, .env.staging, etc. with the appropriate values for each environment.


CI/CD Pipeline

The project includes a GitHub Actions workflow (.github/workflows/ci.yml) with three stages:

  1. Lint & Type Check — ESLint + TypeScript compilation check
  2. Build Check — Prisma client generation + Next.js production build
  3. Docker Build — Multi-stage Docker image build (push: false by default)

To enable Docker image pushes to a registry, add your registry credentials as GitHub Secrets and uncomment the push configuration in ci.yml.

CI Workflow

# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  lint:
    # ESLint + tsc type checking
  test:
    # Prisma generate + Next.js build
  docker:
    # Docker multi-stage build

Health Check & Monitoring

Health Endpoint

The app exposes a health check endpoint at GET /api/health. It returns JSON with connectivity status for the database and Redis (if configured), plus memory and CPU usage:

{
  "status": "healthy",
  "timestamp": "2026-05-28T00:00:00.000Z",
  "uptime": 12345,
  "service": "tpt-police",
  "version": "0.1.0",
  "environment": "production",
  "checks": {
    "database": { "status": "connected", "latencyMs": 2 },
    "redis": { "status": "connected", "latencyMs": 1 }
  },
  "memory": {
    "heapUsed": 45,
    "heapTotal": 78,
    "rss": 120,
    "unit": "MB"
  },
  "cpu": {
    "loadAvg": { "user": 12345, "system": 6789 }
  }
}

Use this endpoint for:

  • Load balancer health checks (configure to hit /api/health)
  • Container orchestration liveness/readiness probes (Kubernetes httpGet)
  • Monitoring tools (Datadog, New Relic, Prometheus, Grafana)

Prometheus Metrics Endpoint

A Prometheus-compatible metrics endpoint is available at GET /api/metrics. It exposes the following metrics in OpenMetrics text format:

MetricTypeDescription
tpt_police_build_infogaugeBuild version, Node version, environment
tpt_police_uptime_seconds_totalcounterApplication uptime in seconds
tpt_police_memory_heap_bytesgaugeHeap used memory in bytes
tpt_police_memory_heap_total_bytesgaugeHeap total memory in bytes
tpt_police_memory_rss_bytesgaugeResident set size in bytes
tpt_police_database_upgaugeDatabase connectivity (1 = connected, 0 = disconnected)
tpt_police_database_latency_msgaugeDatabase query latency in milliseconds

Prometheus scrape config:

scrape_configs:
  - job_name: 'tpt-police'
    scrape_interval: 15s
    static_configs:
      - targets: ['localhost:3000']
    metrics_path: '/api/metrics'

Structured JSON Logging

In production, the application outputs newline-delimited JSON (NDJSON) to stdout/stderr for ingestion by any log aggregation platform:

  • Log levels: debug, info, warn, error, fatal
  • Every log entry includes: timestamp, level, message, service, environment, and optionally requestId, userId, tenantId, durationMs, statusCode, stack
  • Development mode: Colorized human-readable output with contextual badges
  • Log level control: Set LOG_LEVEL environment variable (debug, info, warn, error, fatal). Defaults to info in production, debug in development.

Example production log line:

{"timestamp":"2026-05-28T00:00:00.000Z","level":"info","message":"Request completed","service":"tpt-police","environment":"production","requestId":"abc-123","method":"GET","path":"/api/health","statusCode":200,"durationMs":42}

Integration targets:

PlatformMethod
DatadogAgent tailing stdout/stderr
Grafana LokiPromtail scraping container logs
AWS CloudWatchContainer insights / CloudWatch agent
ELK StackFilebeat → Logstash → Elasticsearch
SplunkUniversal forwarder tailing stdout
Papertrail / LogglyRemote syslog forwarding

Monitoring Recommendations

  • Uptime monitoring: Ping /api/health every 30 seconds; alert on non-200 responses
  • Metrics scraping: Configure Prometheus (or Grafana Cloud) to scrape /api/metrics every 15s
  • Error tracking: Integrate Sentry for exception capture and performance tracing
  • Dashboarding: Import the Prometheus metrics into Grafana for real-time dashboards (memory, DB latency, uptime)
  • Log aggregation: Route container stdout/stderr to your logging platform and create alerts for "level":"error" and "level":"fatal" entries
  • Database monitoring: Use RDS Performance Insights / Cloud SQL Query Insights
  • Alerting rules:
    • Health check returns non-200 for >1 minute
    • Database latency > 500ms
    • Memory usage > 80% of heap total
    • Error rate > 5% of total requests
    • Uptime drops unexpectedly (container restart)

Database Migrations

Development

# Create a new migration after schema changes
npx prisma migrate dev --name describe_change

# Apply migrations
npx prisma migrate dev

Production

# Apply pending migrations (safe for production)
npx prisma migrate deploy

# If you need to reset the database (CAUTION: destructive)
npx prisma migrate reset

CI/CD Migration Strategy

The recommended approach for production deployments:

  1. Before deployment: Run npx prisma migrate deploy as a pre-deployment step
  2. During deployment: The new app version connects to the migrated database
  3. Rollback: If issues arise, roll back the app while the DB schema remains compatible (design migrations to be backward-compatible)

For zero-downtime deployments, use the expand-migrate-contract pattern:

  1. Expand: Add new columns/tables (app can read both old and new)
  2. Migrate: Backfill data, update application logic
  3. Contract: Remove old columns/tables in a subsequent release

Seed Data

For development and testing, seed data can be created via a Prisma seed script (when implemented):

npx prisma db seed

The seed script should create:

  • Super Admin user
  • A Nation-level tenant
  • Sample Province, City, and Precinct tenants
  • Demo users for each role
  • Sample cases, evidence, and other module data

Note: The seed script is not yet implemented. See TODO.md for current status.


Roles & Permissions

RoleScopeCapabilities
SUPER_ADMINNationalPlatform-wide management, tenant onboarding, billing
PROVINCE_ADMINProvinceManage cities/precincts in province, cross-precinct analytics
CITY_ADMINCityManage precincts in city
PRECINCT_ADMINPrecinctFull precinct management (HR, cases, ERP, dispatch)
DETECTIVEAssigned casesCase investigation, evidence management, statements
OFFICERField opsShifts, incidents, vehicle assignments
DISPATCHERDispatchIncident response, unit dispatching
PUBLICPublicCrime map, anonymous tips, FOIA requests

Project Structure

tpt-police/
├── prisma/
│   └── schema.prisma         # Database schema (70+ models)
├── src/
│   ├── app/
│   │   ├── (auth)/           # Login, register, MFA pages
│   │   ├── (platform)/       # Authenticated platform routes
│   │   │   ├── dashboard/    # Role-aware dashboard + shift brief
│   │   │   ├── cases/        # Case management, evidence, persons
│   │   │   ├── dispatch/     # Live dispatch + officer tracking
│   │   │   ├── operations/   # Warrants, bookings, BOLO, use-of-force
│   │   │   ├── evidence-room/# Evidence inventory + lab submissions
│   │   │   ├── bwc/          # Body worn camera management
│   │   │   ├── hr/           # Staff, scheduling, payroll, wellness
│   │   │   ├── erp/          # Fleet, assets, budget, drones
│   │   │   ├── community/    # Events, watch groups, commendations
│   │   │   ├── wellness/     # Mental health check-ins, counselling
│   │   │   ├── reports/      # Analytics and scheduled reports
│   │   │   ├── settings/     # Profile, security (MFA), notifications
│   │   │   └── admin/        # Tenant management, billing, audit
│   │   ├── (public)/         # Public portal (crime map, FOIA, tips)
│   │   └── api/              # 95+ REST API routes
│   ├── components/           # Feature components (co-located)
│   ├── lib/
│   │   ├── ai.ts             # Optional AI service (Ollama + OpenRouter)
│   │   ├── anpr.ts           # Number plate lookup adapter
│   │   ├── auth.ts           # NextAuth configuration
│   │   ├── audit.ts          # Immutable audit log writer
│   │   ├── dispatch.ts       # External CAD adapter
│   │   ├── email.ts          # Multi-provider email (Resend → Mailjet)
│   │   ├── logger.ts         # Structured NDJSON logger
│   │   ├── prisma.ts         # Prisma singleton
│   │   ├── secrets.ts        # Startup secret validation
│   │   └── storage.ts        # R2 / Wasabi abstraction
│   └── middleware.ts         # Route protection + tenant resolution
├── public/
│   ├── manifest.json         # Officer PWA manifest
│   └── public-manifest.json  # Community portal PWA manifest
├── Dockerfile                # Multi-stage production build
├── docker-compose.yml        # App + PostgreSQL + Redis
├── .env.example              # All environment variables documented
├── LICENSE                   # MIT License
└── TODO.md                   # Phase-by-phase task history

API Reference

The application provides REST API routes under /api/. Key endpoints:

EndpointDescription
GET /api/healthHealth check (DB, memory, uptime)
GET /api/metricsPrometheus metrics
GET /api/audit-logsAudit trail (filterable)
GET/POST /api/casesCase management
GET/POST /api/cases/[id]/evidenceEvidence CRUD (tenant-scoped)
GET /api/bwc/camerasBody camera inventory + events
GET /api/lab-submissionsLab submission tracking
GET /api/dispatch/officer-locationsReal-time officer GPS
POST /api/webhooks/dispatchInbound CAD webhook (Bearer auth)
POST /api/vehicles/plate-lookupANPR number plate lookup (audited)
GET /api/shifts/handover-briefShift handover summary (+ AI)
POST /api/admin/foiaFOIA draft/send response (+ AI)
GET /api/admin/ai-configAI provider status
GET/PUT /api/user/profileUser profile management
PUT /api/user/passwordPassword change
GET /api/public/crime-map/heatmapAnonymized public crime data
GET /api/public/alertsPublic safety alerts
POST /api/public/submissionsTips, complaints, commendations

All 95+ routes use Zod validation. Auth via NextAuth.js JWT. Every mutation writes to the audit log.


Known Gaps

GapNotes
No test suiteNo Jest/Vitest setup. Integration tests for RBAC and tenant isolation are strongly recommended before production.
In-memory rate limitingsrc/lib/rate-limit.ts uses an in-memory Map. Set REDIS_URL for multi-instance deployments.
ClamAV scanning optionalFile uploads skip malware scanning unless CLAMD_HOST is set. Not recommended for production without it.
AI is opt-inAI features (FOIA draft, shift brief) degrade gracefully when neither OLLAMA_BASE_URL nor OPENROUTER_API_KEY is set.
ANPR stubPlate lookup returns empty data when ANPR_API_URL is not configured. Safe for development.
PWA icons placeholdermanifest.json references /icons/icon-192.png and /icons/icon-512.png — add real icon files to public/icons/ before deploying as a PWA.

Roadmap

Features planned for future releases:

  • Predictive hotspot mapping — time-of-day/weekday crime heatmap overlays on dispatch and public maps
  • Evidence expiration alerts — automated email/in-app when retention period is approaching
  • Cross-precinct person deduplication — flag when the same person is booked under different names across precincts
  • Court discovery bulk export — one-click download of all case materials for prosecution
  • Voice-to-text field reports — Web Speech API integration for hands-free field contact entry
  • Inter-precinct secure messaging — direct messages and memos across precinct boundaries
  • Offline PWA sync — Background Sync API for queuing field contacts while offline
  • Automated FOIA deadlines — reminder emails to the records officer when FOIA responses are due

License

This project is licensed under the MIT License — see LICENSE for details.