Skip to content

Latest commit

 

History

History
710 lines (579 loc) · 20.1 KB

File metadata and controls

710 lines (579 loc) · 20.1 KB

Project structure

Overview of the OpenJustice codebase organization.

Repository Layout

openjustice/
├── server/          # NestJS 11 backend API (187+ files)
├── client/          # Next.js 16 frontend (69 reusable components)
├── docs/                 # GitBook documentation
└── README.md             # Project overview

Backend Structure (server/)

Root Directory

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)

Source Code Structure (src/)

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

Module Structure Pattern

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

Common Infrastructure (common/)

Configuration (common/config/)

// 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 configuration

Database (common/database/)

database/
├── prisma.service.ts       // Prisma client singleton
└── prisma.module.ts        // Database module export

PrismaService Example:

@Injectable()
export class PrismaService extends PrismaClient
  implements OnModuleInit, OnModuleDestroy {

  async onModuleInit() {
    await this.$connect();
  }

  async onModuleDestroy() {
    await this.$disconnect();
  }
}

Decorators (common/decorators/)

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 auth

Usage 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);
  }
}

DTOs (common/dto/)

Shared data transfer objects:

dto/
├── pagination.dto.ts           // PaginationQueryDto, PaginatedResponseDto
└── base-response.dto.ts        // Standard API response format

Pagination 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),
    };
  }
}

Errors (common/errors/)

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)

Guards (common/guards/)

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 validation

Interceptors (common/interceptors/)

Request/response transformation:

interceptors/
├── transform.interceptor.ts    // Standardize response format
├── logging.interceptor.ts      // Request/response logging
└── audit.interceptor.ts        // Automatic audit logging

Utils (common/utils/)

Utility functions:

utils/
├── encryption.util.ts          // AES-256 encryption/decryption
├── hashing.util.ts            // Argon2id password hashing
└── validation.util.ts         // Custom validation helpers

Database Schema (prisma/)

prisma/
├── 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)

Testing Structure (test/)

test/
├── jest-e2e.json         # E2E test configuration
└── app.e2e-spec.ts      # Health check E2E test

Unit tests are colocated with source files (*.spec.ts).

Frontend Structure (client/)

Root Directory

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 Router Structure (app/)

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 Structure (components/)

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

Library (lib/)

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 (types/)

types/
├── api.ts                # API response types
├── auth.ts               # Authentication types
├── case.ts               # Case-related types
└── person.ts             # Person-related types

Documentation Structure (docs/)

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

Key Files Reference

Configuration Files

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

Entry Points

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

Environment Files

File Purpose Committed?
.env Backend environment variables ❌ No
.env.example Backend env template ✅ Yes
.env.local Frontend environment variables ❌ No

File Naming Conventions

Backend (NestJS)

  • 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

Frontend (Next.js)

  • 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

Import Path Aliases

Backend

No path aliases configured. Use relative imports:

// Good
import { PrismaService } from '../../common/database/prisma.service';

// Avoid absolute paths from src/

Frontend

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 files

Module Dependencies

Backend Module Graph

AppModule
├── 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

Dependency Rules

  1. Common modules can be imported by any feature module
  2. Feature modules should not import each other (use dependency injection)
  3. Services can inject other services via constructor
  4. Controllers should only inject their own service

Code Organization Principles

1. Separation of Concerns

// ❌ 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);
}

2. Single Responsibility

Each file should have one clear purpose:

  • Controllers: HTTP handling only
  • Services: Business logic only
  • Repositories: Database operations only
  • DTOs: Input/output validation only

3. DRY (Don't Repeat Yourself)

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>;
}

4. Dependency Injection

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) {}
}

Navigation Guide

Finding Specific Code

"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) or src/**/*.spec.ts (unit)

Common Workflows

Adding a new feature:

  1. Create module: src/modules/feature/
  2. Define DTOs: src/modules/feature/dto/
  3. Create repository: src/modules/feature/feature.repository.ts
  4. Create service: src/modules/feature/feature.service.ts
  5. Create controller: src/modules/feature/feature.controller.ts
  6. Create module: src/modules/feature/feature.module.ts
  7. Import in src/app.module.ts

Modifying database schema:

  1. Edit: prisma/schema.prisma
  2. Format: npx prisma format
  3. Migrate: npx prisma migrate dev --name change-description
  4. Generate: npx prisma generate (usually automatic)
  5. Update repositories to use new fields

Adding a new API endpoint:

  1. Define DTO in dto/
  2. Add method to service
  3. Add controller endpoint
  4. Test with Postman/Insomnia
  5. Update API documentation

Next Steps