- Backend: ShallowEtagHeaderFilter for /api/v1/*, API-VERSIONING.md, README (tenant, CORS, Flyway, ETag) - k8s: backend-deployment.yaml (Deployment, Service, Secret/ConfigMap) - Web: scaffold with directory pull, 304 handling, touch-friendly UI - Android 16: ANDROID-16-TARGET.md; BuildConfig STUN/signaling, SMOAApplication configures InfrastructureManager - Domain: CertificateManager revocation stub, ReportService signReports, ZeroTrust/ThreatDetection minimal docs - TODO.md and IMPLEMENTATION_STATUS.md updated; communications README for endpoint config Co-authored-by: Cursor <cursoragent@cursor.com>
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)
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
- Point the app’s sync base URL to this backend (e.g.
http://<host>:8080). - 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.
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).