- Extend RBAC modules/permissions; Home + drawer + Nav routes for ATF/NCIC/military/judicial/intelligence - Wire Orders/Evidence/Reports/ATF/NCIC/Military/Judicial/Intelligence Compose flows with Hilt ViewModels - Evidence: custody chain merge in repository; list/detail UI; fix transfer entity evidenceId - SyncService: hydrate credential payloads after list pull; CredentialCacheSyncPort API + tests - CredentialCacheMerger hydrateMissingPayloads; CredentialsViewModel refresh/hydrate split - OrderServiceTest; SyncService + CredentialCacheMerger tests; NCIC operator display - docs: OUTSTANDING-PRODUCTION-AND-COMPLIANCE-WORK.md; GAPS updates; README implementation status - Misc doc/build updates (OpenAPI test, compliance matrix, etc.) Made-with: Cursor
SMOA Backend
Ground-up backend with REST APIs for the Secure Mobile Operations Application (SMOA) Android app. Provides sync endpoints for directory, orders, evidence, credentials, and reports, with optional API key auth and OpenAPI docs.
Requirements
- JDK 17
- Gradle 8.x (wrapper included in repo root; run from
backend/with../gradlewor install Gradle) - Production DB: PostgreSQL 14+ recommended. The PostgreSQL JDBC driver is on the classpath; set
SPRING_DATASOURCE_URL,SPRING_DATASOURCE_USERNAME,SPRING_DATASOURCE_PASSWORD(orspring.datasource.*) and run withSPRING_PROFILES_ACTIVE=prod.
Quick Start
cd backend
../gradlew bootRun
Or with explicit profile:
../gradlew bootRun --args='--spring.profiles.active=dev'
- API base:
http://localhost:8080 - Health:
GET http://localhost:8080/health - API info:
GET http://localhost:8080/api/v1/info - Swagger UI:
http://localhost:8080/swagger-ui.html - OpenAPI JSON:
http://localhost:8080/v3/api-docs
Sync API (for the mobile app)
All sync endpoints accept JSON and return a SyncResponse that matches the mobile SyncAPI contract in core/common/SyncAPI.kt:
| Endpoint | Method | Request body | Response |
|---|---|---|---|
/api/v1/sync/directory |
POST | DirectorySyncRequest |
SyncResponse |
/api/v1/sync/order |
POST | OrderSyncRequest |
SyncResponse |
/api/v1/sync/evidence |
POST | EvidenceSyncRequest |
SyncResponse |
/api/v1/sync/credential |
POST | CredentialSyncRequest |
SyncResponse |
/api/v1/sync/report |
POST | ReportSyncRequest |
SyncResponse |
Delete (sync delete): DELETE /api/v1/sync/directory/{id}, DELETE /api/v1/sync/order/{orderId}, DELETE /api/v1/sync/evidence/{evidenceId}, DELETE /api/v1/sync/credential/{credentialId}, DELETE /api/v1/sync/report/{reportId} — each returns SyncResponse.
Pull / GET (refresh or initial load): GET /api/v1/directory (optional unit, X-Unit), GET /api/v1/orders (since, limit, jurisdiction / X-Unit), GET /api/v1/evidence, GET /api/v1/credentials, GET /api/v1/reports (since, limit, optional filters).
SyncResponse fields: success, itemId, serverTimestamp, conflict, remoteData (optional), message (optional). When conflict: true, remoteData is base64-encoded JSON of the server version.
Conflict detection: send lastUpdated (directory) or clientUpdatedAt (others). If the server has a newer version, the response has conflict: true and remoteData with the server payload.
Authentication
- Development: No API key required when
smoa.api.keyis empty (default indevprofile). - Production: Set
SMOA_API_KEY(orsmoa.api.key). Clients must send:- Header:
X-API-Key: <key>, or - Query:
?api_key=<key>
- Header:
Configuration
| Property | Default | Description |
|---|---|---|
server.port |
8080 | Server port |
spring.datasource.url |
H2 file ./data/smoa |
DB URL (use PostgreSQL in production) |
smoa.api.key |
(empty) | API key; empty = no auth |
smoa.api.key-header |
X-API-Key | Header name for API key |
smoa.cors.allowed-origins |
* | CORS origins (comma-separated); * = any, no credentials |
smoa.rate-limit.enabled |
true | Rate limit on /api/v1/* (per API key or IP) |
smoa.rate-limit.requests-per-minute |
120 | Max requests per minute; 429 when exceeded |
smoa.tenant.require-unit |
false | When true, require X-Unit (or unit query) for all /api/v1 requests |
smoa.cors.allowed-origins |
* | Production: set to your web app origin(s) (e.g. https://smoa.example.com) for CORS |
Tracing: Each request gets an X-Request-Id header (or preserves incoming one); use for logs and support.
Caching: GET list endpoints support ETag and If-None-Match; send If-None-Match: <etag> to receive 304 Not Modified when unchanged.
Profiles:
- dev – relaxed auth, H2 console at
/h2-console, debug logging. - prod – set
SPRING_PROFILES_ACTIVE=prodandSMOA_API_KEY, and switch datasource to PostgreSQL as needed.
Database
- Default: H2 file database at
./data/smoa. Flyway runs migrations fromdb/migration/;ddl-auto: updatein dev. - Production: Use PostgreSQL: set
spring.datasource.url=jdbc:postgresql://...,driver-class-name=org.postgresql.Driver, and addorg.postgresql:postgresqldependency. Useddl-auto: validate(set inapplication-prod.yml) so Flyway owns the schema.
Building
cd backend
../gradlew build
JAR:
../gradlew bootJar
# output: build/libs/smoa-backend-1.0.0.jar
java -jar build/libs/smoa-backend-1.0.0.jar
Docker (build from repo root):
docker build -f backend/Dockerfile .
docker run -p 8080:8080 <image-id>
Sync and delete operations are audit logged (resource type, id, operation, success).
Connecting the Android app
Gradle (examples): -Psmoa.backend.baseUrl=, -Psmoa.api.key=, -Psmoa.config.url= (hosted JSON; load is non-blocking — Retrofit resolves URL on each call after prefs update), -Psmoa.stun.urls= / -Psmoa.signaling.urls=, -Psmoa.browser.vpnEnforced=true (in-app browser requires VPN when enabled).
- Point the app’s sync base URL to this backend (e.g.
http://<host>:8080), or rely on hosted config — seedocs/examples/smoa-client-config.example.json. - Use
CredentialSyncRequestDto(or an equivalent map) when queueing credential sync — seecore/common/.../CredentialSyncRequestDto.kt. - Implement a real
SyncAPI(e.g. with Retrofit) that:- Serializes domain models to JSON matching the backend DTOs (
DirectorySyncRequest,OrderSyncRequest, etc.). - POSTs to
/api/v1/sync/directory,/api/v1/sync/order, etc. - Parses
SyncResponse(and handlesconflict/remoteDatawhen present).
- Serializes domain models to JSON matching the backend DTOs (
Request DTOs align with the app’s directory, order, evidence, report, and credential concepts; field names and types are chosen for easy mapping from the mobile side.
Deployment and client configuration
- Proxmox LXC layout, FQDNs, and client JSON contract: docs/LXC-PROXMOX-CONTAINERS.md
- Example client config (for CI / static hosting): docs/examples/smoa-client-config.example.json
Identity templates (Complete Credential alignment)
Credential sync types and mobile barcode templates are aligned with the Complete Credential shared schema (CredentialRef.domain) and SMOA formats (ICAO 9303, AAMVA, MIL-STD-129). See ../docs/reference/IDENTITY-TEMPLATE-ALIGNMENT.md and backend SmoaCredentialType.
Umbrella program: integration boundary and runbook — complete-credential/docs/integrations/smoa.md and ADR-0010 (adr/0010-smoa-mobile-integration-boundary.md) when the complete-credential repo is available. Drift check: SMOA_REPO + tools/verify/verify-smoa-cc-domain-alignment.sh from that repo root.
Gap analysis and roadmap
See docs/BACKEND-GAPS-AND-ROADMAP.md for a full review: what's covered, completed gaps (delete sync, pull/GET, enum validation, rate limiting, audit, tests, Dockerfile), and optional follow-ups (prod profile, unit/tenant scoping, migrations).