Overview of the OpenJustice codebase organization.
openjustice/
├── server/ # NestJS 11 backend API (187+ files)
├── client/ # Next.js 16 frontend (69 reusable components)
├── docs/ # GitBook documentation
└── README.md # Project overview
server/
├── src/ # Application source code
├── test/ # E2E tests
├── prisma/ # Database schema & migrations
├── dist/ # Compiled output (generated)
├── node_modules/ # Dependencies (generated)
├── package.json # Dependencies & scripts
├── tsconfig.json # TypeScript configuration
├── nest-cli.json # NestJS CLI configuration
├── eslint.config.mjs # ESLint configuration
├── .prettierrc # Prettier configuration
├── prisma.config.ts # Prisma 7 database URL config
└── .env # Environment variables (not in git)
src/
├── main.ts # Application entry point
├── app.module.ts # Root module (imports all features)
├── common/ # Shared infrastructure (59 files)
│ ├── config/ # Configuration management
│ ├── database/ # Prisma service & base repository
│ ├── decorators/ # Custom decorators (@Public, @CurrentUser, etc.)
│ ├── domain/ # Domain interfaces & base classes
│ ├── dto/ # Common DTOs (pagination, response)
│ ├── errors/ # Custom exception classes
│ ├── filters/ # Exception filters
│ ├── guards/ # Authentication guards (JWT, roles)
│ ├── interceptors/ # Request/response interceptors
│ ├── pipes/ # Validation pipes
│ └── utils/ # Utility functions (encryption, hashing)
├── health/ # Health check endpoints
└── modules/ # Feature modules (21 modules, 168 files)
├── auth/ # Authentication (login, PIN management)
├── officers/ # Officer management
├── roles/ # RBAC roles & permissions
├── stations/ # Police stations
├── persons/ # Person records
├── cases/ # Case management
├── evidence/ # Evidence tracking
├── alerts/ # Amber alerts & wanted persons
├── background-checks/ # Background verification
├── vehicles/ # Vehicle records
├── audit/ # Audit logging
├── sync/ # Offline synchronization
├── upload/ # File upload service
├── reports/ # Report generation
├── analytics/ # Data analytics
├── ussd/ # USSD interface
├── whatsapp/ # WhatsApp integration
├── bulk-import/ # Bulk data import
├── geocrime/ # Geographic crime analysis
├── interagency/ # External agency integration
└── framework-config/ # Multi-country configuration
Every feature module follows this consistent structure:
modules/example/
├── example.module.ts # Module definition
├── example.controller.ts # HTTP endpoints
├── example.service.ts # Business logic
├── example.repository.ts # Database operations
├── dto/ # Data transfer objects
│ ├── create-example.dto.ts # Input validation for creation
│ ├── update-example.dto.ts # Input validation for updates
│ └── example-response.dto.ts # Output format
└── example.service.spec.ts # Unit tests (colocated with source)
Example - Cases Module:
// modules/cases/
// ├── cases.module.ts (Module registration)
// ├── cases.controller.ts (API endpoints: GET/POST/PATCH)
// ├── cases.service.ts (Business logic, validation)
// ├── cases.repository.ts (Database queries via Prisma)
// └── dto/
// ├── create-case.dto.ts
// ├── update-case.dto.ts
// ├── case-filters.dto.ts
// └── case-response.dto.ts// Configuration classes using @nestjs/config
config/
├── app.config.ts // Application settings (port, environment)
├── database.config.ts // Database connection settings
├── jwt.config.ts // JWT token configuration
├── s3.config.ts // S3 storage configuration
└── redis.config.ts // Redis queue configurationdatabase/
├── prisma.service.ts // Prisma client singleton
└── prisma.module.ts // Database module exportPrismaService Example:
@Injectable()
export class PrismaService extends PrismaClient
implements OnModuleInit, OnModuleDestroy {
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}Custom decorators for cleaner code:
decorators/
├── public.decorator.ts // @Public() - Skip JWT auth
├── roles.decorator.ts // @Roles(...) - RBAC guard
├── current-user.decorator.ts // @CurrentUser() - Extract user from JWT
├── current-officer.decorator.ts // @CurrentOfficer() - Extract officer
└── api-key.decorator.ts // @ApiKey() - External API authUsage Example:
@Controller('cases')
@UseGuards(JwtAuthGuard, RolesGuard)
export class CasesController {
@Post()
@Roles('Officer', 'Admin')
async create(
@Body() dto: CreateCaseDto,
@CurrentOfficer() officer: Officer
) {
return this.casesService.create(dto, officer.id);
}
@Get('public/status/:caseNumber')
@Public() // Skip authentication
async getPublicStatus(@Param('caseNumber') caseNumber: string) {
return this.casesService.getPublicStatus(caseNumber);
}
}Shared data transfer objects:
dto/
├── pagination.dto.ts // PaginationQueryDto, PaginatedResponseDto
└── base-response.dto.ts // Standard API response formatPagination Example:
export class PaginationQueryDto {
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
page?: number = 1;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
@Max(100)
limit?: number = 20;
get skip(): number {
return (this.page - 1) * this.limit;
}
get take(): number {
return this.limit;
}
}
export class PaginatedResponseDto<T> {
data: T[];
pagination: {
total: number;
page: number;
limit: number;
totalPages: number;
};
constructor(data: T[], total: number, page: number, limit: number) {
this.data = data;
this.pagination = {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
};
}
}Custom exception hierarchy:
errors/
├── business-rule.exception.ts // Domain logic violations
├── not-found.exception.ts // Resource not found (404)
├── unauthorized.exception.ts // Authentication failure (401)
└── forbidden.exception.ts // Authorization failure (403)Authentication and authorization:
guards/
├── jwt-auth.guard.ts // JWT token validation
├── roles.guard.ts // Role-based access control
└── api-key-auth.guard.ts // External API key validationRequest/response transformation:
interceptors/
├── transform.interceptor.ts // Standardize response format
├── logging.interceptor.ts // Request/response logging
└── audit.interceptor.ts // Automatic audit loggingUtility functions:
utils/
├── encryption.util.ts // AES-256 encryption/decryption
├── hashing.util.ts // Argon2id password hashing
└── validation.util.ts // Custom validation helpersprisma/
├── schema.prisma # Database schema (30 models)
├── seed.ts # Initial data seeding script
└── migrations/ # Migration history
└── YYYYMMDDHHMMSS_migration_name/
└── migration.sql
Key Models:
// Authentication & Authorization
Officer (users)
Role (RBAC roles)
Permission (granular permissions)
Session (JWT sessions)
// Core Domain
Person (individuals in system)
Case (criminal cases)
Evidence (case evidence)
CasePerson (case-person relationships)
CaseEvidence (case-evidence relationships)
// Alerts & Checks
AmberAlert (missing persons)
WantedPerson (arrest warrants)
BackgroundCheck (verification requests)
// Organization
Station (police stations)
Vehicle (vehicle records)
// Integration
USSDQueryLog (USSD usage tracking)
WhatsAppSession (WhatsApp user sessions)
WhatsAppNewsletter (WhatsApp broadcasts)
Agency (external agencies)
AgencyWebhook (interagency webhooks)
// Analytics
GeoCrimeAggregate (crime heatmaps)
// System
AuditLog (immutable audit trail)
SyncQueue (offline sync queue)
CaseNote (case notes with redaction)test/
├── jest-e2e.json # E2E test configuration
└── app.e2e-spec.ts # Health check E2E test
Unit tests are colocated with source files (*.spec.ts).
client/
├── app/ # Next.js 16 App Router
├── components/ # React components (69 reusable components)
├── lib/ # Utilities & API client
├── hooks/ # Custom React hooks
├── types/ # TypeScript type definitions
├── public/ # Static assets
├── .next/ # Build output (generated)
├── node_modules/ # Dependencies (generated)
├── package.json # Dependencies & scripts
├── next.config.ts # Next.js configuration
├── tsconfig.json # TypeScript configuration
├── vitest.config.ts # Vitest test configuration
├── vitest.setup.ts # Test setup (testing-library)
└── .env.local # Environment variables (not in git)
app/
├── layout.tsx # Root layout
├── page.tsx # Homepage
├── (auth)/ # Authentication pages (grouped route)
│ ├── login/
│ └── change-pin/
├── (dashboard)/ # Protected dashboard (grouped route)
│ ├── layout.tsx # Dashboard layout with nav
│ ├── cases/
│ ├── persons/
│ ├── evidence/
│ ├── alerts/
│ ├── background-checks/
│ ├── analytics/
│ └── settings/
└── api/ # API routes (Next.js middleware)
components/
├── ui/ # Shadcn UI primitives (24 components)
│ ├── button.tsx
│ ├── input.tsx
│ ├── dialog.tsx
│ └── ...
├── auth/ # Authentication components
│ ├── login-form.tsx
│ ├── pin-input.tsx
│ └── mfa-setup.tsx
├── cases/ # Case management components
│ ├── case-list.tsx
│ ├── case-detail.tsx
│ ├── case-form.tsx
│ └── case-status-badge.tsx
├── persons/ # Person record components
├── evidence/ # Evidence tracking components
├── layout/ # Layout components
│ ├── header.tsx
│ ├── sidebar.tsx
│ └── mobile-nav.tsx
├── pwa/ # PWA & offline components
│ ├── install-prompt.tsx
│ ├── offline-indicator.tsx
│ └── sync-status.tsx
└── shared/ # Shared components
├── data-table.tsx
├── search-bar.tsx
└── pagination.tsx
lib/
├── api-client.ts # Axios client for backend API
├── auth.ts # NextAuth configuration
├── hooks/ # Custom React hooks
│ ├── use-auth.ts
│ ├── use-cases.ts
│ └── use-sync.ts
└── utils/
├── date.ts # Date formatting utilities
├── validation.ts # Form validation helpers
└── permissions.ts # Permission checking
types/
├── api.ts # API response types
├── auth.ts # Authentication types
├── case.ts # Case-related types
└── person.ts # Person-related types
docs/
├── README.md # Documentation home
├── SUMMARY.md # GitBook sidebar
├── .gitbook.yaml # GitBook configuration
├── 01-introduction/ # Project overview
├── 02-getting-started/ # Setup guides
├── 03-architecture/ # System design
├── 04-api-reference/ # API documentation
├── 05-guides/ # Usage & development guides
├── 06-frontend/ # Frontend documentation
└── 07-appendices/ # Additional resources
| File | Purpose |
|---|---|
tsconfig.json |
TypeScript compiler options |
nest-cli.json |
NestJS CLI configuration |
eslint.config.mjs |
Linting rules |
.prettierrc |
Code formatting rules |
prisma.config.ts |
Prisma 7 database URL (required!) |
package.json |
Dependencies & npm scripts |
| File | Purpose |
|---|---|
src/main.ts |
NestJS application bootstrap |
src/app.module.ts |
Root module with all imports |
app/layout.tsx |
Next.js root layout |
app/page.tsx |
Homepage |
| File | Purpose | Committed? |
|---|---|---|
.env |
Backend environment variables | ❌ No |
.env.example |
Backend env template | ✅ Yes |
.env.local |
Frontend environment variables | ❌ No |
- Modules:
feature.module.ts - Controllers:
feature.controller.ts - Services:
feature.service.ts - Repositories:
feature.repository.ts - DTOs:
create-feature.dto.ts,update-feature.dto.ts - Interfaces:
feature.interface.ts - Types:
feature.types.ts - Tests:
feature.service.spec.ts,feature.e2e-spec.ts
- Pages:
page.tsx(App Router convention) - Layouts:
layout.tsx - Components:
component-name.tsx(kebab-case) - Hooks:
use-feature.ts - Types:
feature.types.ts - Utils:
feature.util.ts
No path aliases configured. Use relative imports:
// Good
import { PrismaService } from '../../common/database/prisma.service';
// Avoid absolute paths from src/TypeScript paths configured in tsconfig.json:
{
"compilerOptions": {
"paths": {
"@/*": ["./*"]
}
}
}Usage:
// Good
import { Button } from '@/components/ui/button';
import { useAuth } from '@/lib/hooks/use-auth';
// Avoid relative imports for distant filesAppModule
├── ConfigModule (global)
├── PrismaModule (global)
├── AuthModule
│ ├── JwtModule
│ └── PassportModule
├── OfficersModule
├── RolesModule
├── StationsModule
├── PersonsModule
├── CasesModule
│ ├── PersonsModule (imported)
│ └── EvidenceModule (imported)
├── EvidenceModule
│ └── UploadModule (imported)
├── AlertsModule
├── BackgroundChecksModule
├── VehiclesModule
├── AuditModule
├── SyncModule
├── UploadModule
├── ReportsModule
├── AnalyticsModule
├── USSDModule
├── WhatsAppModule
├── BulkImportModule
├── GeoCrimeModule
├── InteragencyModule
└── FrameworkConfigModule
- Common modules can be imported by any feature module
- Feature modules should not import each other (use dependency injection)
- Services can inject other services via constructor
- Controllers should only inject their own service
// ❌ Bad - Business logic in controller
@Post()
async create(@Body() dto: CreateCaseDto) {
const caseNumber = await this.generateCaseNumber();
const case = await this.prisma.case.create({...});
await this.auditLog.create({...});
return case;
}
// ✅ Good - Delegate to service
@Post()
async create(@Body() dto: CreateCaseDto, @CurrentOfficer() officer: Officer) {
return this.casesService.create(dto, officer.id);
}Each file should have one clear purpose:
- Controllers: HTTP handling only
- Services: Business logic only
- Repositories: Database operations only
- DTOs: Input/output validation only
Extract common logic to utilities or base classes:
// Base repository with common CRUD operations
export abstract class BaseRepository<T> {
abstract findById(id: string): Promise<T | null>;
abstract findAll(filters: any): Promise<T[]>;
abstract create(data: any): Promise<T>;
abstract update(id: string, data: any): Promise<T>;
abstract delete(id: string): Promise<void>;
}Use NestJS dependency injection instead of manual instantiation:
// ❌ Bad
const service = new CasesService(new CasesRepository(new PrismaService()));
// ✅ Good
@Injectable()
export class CasesService {
constructor(private readonly casesRepository: CasesRepository) {}
}"Where do I find..."
- API endpoints? →
src/modules/*/**.controller.ts - Business logic? →
src/modules/*/**.service.ts - Database queries? →
src/modules/*/**.repository.ts - Validation rules? →
src/modules/*/dto/**.dto.ts - Database schema? →
prisma/schema.prisma - Authentication? →
src/modules/auth/+src/common/guards/ - Error handling? →
src/common/errors/+src/common/filters/ - Configuration? →
src/common/config/ - Utilities? →
src/common/utils/ - Tests? →
test/(E2E) orsrc/**/*.spec.ts(unit)
Adding a new feature:
- Create module:
src/modules/feature/ - Define DTOs:
src/modules/feature/dto/ - Create repository:
src/modules/feature/feature.repository.ts - Create service:
src/modules/feature/feature.service.ts - Create controller:
src/modules/feature/feature.controller.ts - Create module:
src/modules/feature/feature.module.ts - Import in
src/app.module.ts
Modifying database schema:
- Edit:
prisma/schema.prisma - Format:
npx prisma format - Migrate:
npx prisma migrate dev --name change-description - Generate:
npx prisma generate(usually automatic) - Update repositories to use new fields
Adding a new API endpoint:
- Define DTO in
dto/ - Add method to service
- Add controller endpoint
- Test with Postman/Insomnia
- Update API documentation
- Creating Modules - Learn how to add new features
- Database Migrations - Manage schema changes
- Testing - Write tests
- Code Style - Follow conventions