A production-grade API engine for automated driving license testing systems. Manages test orchestration, real-time video analysis coordination, payment processing, and result reporting across multiple test centers and devices.
- Go 1.24 or later
- PostgreSQL 14+
- Docker & Docker Compose (optional)
# Clone and configure
git clone <repository-url>
cd adlts-core-engine
cp .env.example .env
# Set required variables
export DATABASE_URL="postgres://user:pass@localhost:5432/adlts"
export JWT_SECRET="your-32-byte-random-key"
# Run migrations
psql $DATABASE_URL < migrations/001_schema.sql
psql $DATABASE_URL < migrations/002_testing_core.sql
psql $DATABASE_URL < migrations/003_create_appeal_evidence.sql
# Start the server
go run ./cmd/api
# Server listening on http://localhost:8080
curl http://localhost:8080/health
# Seed integration data
DATABASE_URL=$DATABASE_URL go run ./cmd/seed/main.goSeeded login accounts:
super@adlts.et/SuperAdmin123!admin@adlts.et/Admin123!candidate@test.et/Candidate123!institute@test.et/Institute123!expert@test.et/Expert123!authority@test.et/Authority123!
Or with Docker:
docker-compose up -d
# API available at http://localhost:8080- Features
- Architecture
- Configuration
- API Usage
- Development
- Database
- Deployment
- Contributing
- Troubleshooting
- License
- Test Execution Orchestration: State machine-driven exam lifecycle with atomic database transactions
- Real-Time Video Analysis: HTTP integration with Python FastAPI computer vision service for lane detection and traffic rule compliance scoring
- Multi-Role Access Control: Nine entity types with fine-grained role-based authorization (super-admin, examiner, candidate, institute, expert, etc.)
- Payment Processing: Chapa payment provider integration with webhook support and transaction reconciliation
- Recording Archival: MinIO S3-compatible storage for test session recordings and frame-level analysis results
- Automated Workflows: Background jobs for test expiry, notification delivery, and state synchronization
- Result Reporting: PDF generation with AI-powered narrative summaries via Gemini API integration
- Authentication: JWT-based token system with bcrypt password hashing and stateless session management
- Audit Trails: Complete operation history with actor identity and timestamps on all mutable entities
- Email Delivery: SMTP integration for one-time passwords, password resets, and notifications
┌─────────────────┐
│ Client Apps │ (Web, Mobile)
└────────┬────────┘
│ REST API
│
┌────▼──────────────────────────┐
│ ADLTS Core Engine (Go) │
│ ───────────────────────────── │
│ • Identity & Auth │
│ • Booking & Payment │
│ • Test Orchestration │
│ • Recording Coordination │
│ • Result Reporting │
│ • Appeals Management │
└────┬─────────────┬──────────────┘
│ │
┌────▼──┐ ┌────▼─────────────┐
│ PgSQL │ │ Python CV Service │
│ │ │ (Lane Detection) │
└───────┘ └─────┬────────────┘
│
┌────▼─────┐
│ ESP32-CAM │
│ (Device) │
└───────────┘
| Module | Purpose | Key Entities |
|---|---|---|
| Identity | User authentication, account management, role assignment | Users, roles, credentials |
| Booking | Scheduling, payment processing, confirmation workflows | Bookings, payments, invoices |
| Testing | Core exam lifecycle, device management, scoring | Tests, devices, sessions, results |
| Recording | Video archival and playback coordination | Recordings, frame analyses |
| Appeals | Test result challenges with evidence | Appeals, evidence, verdicts |
| Reporting | PDF report generation with analytics | Reports, narratives, statistics |
- Database Layer: pgx connection pooling with row-level locking and atomic transactions
- Security: JWT tokens, bcrypt hashing, CORS middleware, role-based authorization
- HTTP Router: chi/v5 composable middleware for logging, recovery, timeout enforcement
- File Management: Upload validation, media streaming, byte-range request support
- Object Storage: MinIO S3-compatible API for recordings and analysis artifacts
- Email: SMTP delivery with TLS/STARTTLS support and text/HTML message bodies
- External Services: Chapa (payments), Gemini (AI summaries), Python CV (video analysis)
Required variables:
DATABASE_URL=postgres://user:password@localhost:5432/adlts_db
JWT_SECRET=<32-byte-random-string>
PORT=8080Optional variables with defaults:
| Variable | Default | Description |
|---|---|---|
INTERNAL_API_KEY |
empty | Shared secret for internal service-to-service calls |
SUPER_ADMIN_EMAIL |
root@adlts.et | Initial admin account email |
SUPER_ADMIN_PASSWORD |
empty | Initial admin password (auto-generated if empty) |
UPLOADS_DIR |
../uploads | Base directory for file uploads |
MEDIA_MAX_SIZE_MB |
5 | Maximum upload size in MB |
CHAPA_SECRET_KEY |
empty | Payment provider API key |
CHAPA_BASE_URL |
https://api.chapa.co/v1 | Payment provider endpoint |
SMTP_HOST |
empty | Email server hostname |
SMTP_PORT |
587 | Email server port |
SMTP_USER |
empty | Email server username |
SMTP_PASSWORD |
empty | Email server password |
SMTP_FROM |
noreply@adlts.et | Sender email address |
SMTP_FROM_NAME |
ADLTS | Sender display name |
SMTP_ENCRYPTION |
auto | SMTP encryption mode: auto, starttls, tls, or none |
SMTP_TIMEOUT_SECONDS |
10 | SMTP connection and delivery timeout |
MINIO_ENDPOINT |
localhost:9000 | Object storage endpoint |
MINIO_BUCKET |
recordings | Storage bucket name |
LANE_DETECTOR_URL |
http://localhost:8000 | Python CV service URL |
GEMINI_API_KEY |
empty | AI text generation API key |
For local development, docker compose up starts Mailpit and configures the API to send OTP, password reset, and invitation emails to it. Open http://localhost:8025 to inspect captured messages.
For production, set SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM, and the appropriate SMTP_ENCRYPTION value for your provider.
All authenticated endpoints require a bearer token in the Authorization header:
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
http://localhost:8080/api/v1/testsTests — Manage exam lifecycle:
# Create test
POST /api/v1/tests
{
"candidate_id": "uuid",
"test_plan_id": "uuid",
"booking_id": "uuid"
}
# Get test status
GET /api/v1/tests/:id
# Transition test state
POST /api/v1/tests/:id/begin-exam
POST /api/v1/tests/:id/end-exam
POST /api/v1/tests/:id/abortDevices — Register and monitor hardware:
# Register device
POST /api/v1/devices
{
"device_code": "DEVICE_001",
"stream_url": "http://192.168.1.10/stream"
}
# Health check
GET /api/v1/devices/:id/healthBookings — Schedule exams with payment:
# Create booking
POST /api/v1/bookings
{
"candidate_id": "uuid",
"test_plan_id": "uuid",
"scheduled_at": "2026-06-15T10:00:00Z"
}
# Confirm payment
POST /api/v1/bookings/:id/confirm-paymentSee TESTING_CORE_API_DOCUMENTATION.md for complete endpoint reference.
adlts-core-engine/
├── cmd/api/ # Application entry point
├── internal/
│ ├── app/ # Service composition
│ ├── identity/ # Authentication & user management
│ ├── booking/ # Scheduling & payments
│ ├── testing/ # Core exam orchestration
│ ├── recording/ # Video archival
│ ├── appeal/ # Result disputes
│ ├── reporting/ # Report generation
│ └── platform/ # Cross-cutting concerns
│ ├── config/ # Environment binding
│ ├── db/ # Database connection
│ ├── security/ # JWT, authorization
│ ├── media/ # File serving
│ ├── minio/ # Object storage
│ └── mailer/ # Email delivery
├── migrations/ # Database schema versions
├── tests/ # Integration tests
├── Dockerfile # Container definition
└── go.mod # Dependencies
# Install dependencies
go mod download
# Build binary
go build -o adlts-api ./cmd/api
# Run tests
go test ./...
# Build Docker image
docker build -t adlts-core-engine .The codebase follows standard Go project layout:
- Domain models in
internal/domain/— shared entities and enums - Service logic in
internal/{module}/service.go— business rules - Data access in
internal/{module}/repository.go— database operations - HTTP handlers in
internal/{module}/handler.go— request/response mapping - Platform utilities in
internal/platform/— reusable infrastructure
- Create a feature branch:
git checkout -b feature/description - Make changes and add tests
- Run linter:
go fmt ./...andgo vet ./... - Commit with clear messages:
git commit -m "feat: add X feature" - Push and open a pull request
Database migrations are versioned SQL files in migrations/:
# Apply all migrations
for file in migrations/*.sql; do
psql $DATABASE_URL < "$file"
done
# Or apply individually
psql $DATABASE_URL < migrations/001_schema.sqlidentities— Users with roles and credentialstests— Exam records with state and scoringbookings— Schedule entries with payment statusdevices— Hardware registration and statussessions— Maneuver attempt recordsframe_analyses— Per-frame CV resultsappeals— Test result challenges
Test state transitions use database-level locking to prevent race conditions:
// Atomic status transition
UPDATE tests SET status='running' WHERE id=$1 AND status='ready'The WHERE clause acts as optimistic concurrency control — if the condition fails, the transition is rejected.
Start the complete stack with a single command:
docker-compose up -dThis starts:
- ADLTS API on port 8080
- PostgreSQL on port 5432
- MinIO object storage on port 9000
For production, use environment-specific configurations:
- Set all required environment variables via secrets manager
- Use a managed PostgreSQL database (AWS RDS, Azure, etc.)
- Use a managed S3-compatible storage (AWS S3, MinIO enterprise, etc.)
- Enable HTTPS with a reverse proxy (Nginx, Caddy)
- Monitor logs and metrics via your platform (CloudWatch, DataDog, etc.)
If Render logs show 127.0.0.1:5432 or [::1]:5432 connection refused, the web service is using a local development DATABASE_URL. On Render, localhost is the API container itself, not your Postgres database.
Set the backend web service environment variable:
DATABASE_URL=postgresql://USER:PASSWORD@INTERNAL_HOST:5432/DATABASEUse the Internal Database URL from your Render Postgres database when the web service and database are in the same account and region. Remove any DATABASE_URL=postgres://...@localhost:5432/... value from the service environment or attached environment groups, then redeploy.
Required Render environment variables include:
DATABASE_URL=<Render Postgres Internal Database URL>
JWT_SECRET=<strong production secret>
BASE_URL=https://<your-backend>.onrender.com
FRONTEND_BASE_URL=https://<your-frontend-domain>
CORS_ALLOWED_ORIGINS=https://<your-frontend-domain>Kubernetes-style health endpoints:
# Liveness probe
curl http://localhost:8080/health
# Readiness (checks DB connection)
curl http://localhost:8080/api/v1/health"DATABASE_URL is required"
Set the environment variable before running:
export DATABASE_URL="postgres://user:password@localhost:5432/adlts"
go run ./cmd/api"connection refused" on startup
Ensure PostgreSQL is running and accessible:
psql $DATABASE_URL -c "SELECT 1"Migration errors
Check that all migration files exist and are in order:
ls -la migrations/JWT token expired
Tokens expire after a set duration. Request a new token by re-authenticating:
POST /api/v1/auth/loginEnable detailed logging:
LOGLEVEL=debug go run ./cmd/apiFor high-load scenarios:
- Increase PostgreSQL connection pool:
PGBOUNCER_MAX_CLIENT_CONN=1000 - Enable Redis caching (not yet implemented, planned for v2)
- Scale API horizontally behind a load balancer
See TESTING_CORE_API_DOCUMENTATION.md for detailed API reference and docs/ for architecture decisions.
Contributions are welcome. Please follow these guidelines:
- Fork the repository
- Create a feature branch with clear naming:
feature/user-auth,fix/state-transition - Write tests for new functionality
- Ensure all tests pass:
go test ./... - Keep commits atomic and messages descriptive
- Submit a pull request with context
Code style follows Go conventions enforced by go fmt and go vet.
MIT License — see LICENSE for details.
Copyright (c) 2026 ADLTS-Lab
Questions? Check the documentation or open an issue on GitHub.