# 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 `../gradlew` or install Gradle) ## Quick Start ```bash cd backend ../gradlew bootRun ``` Or with explicit profile: ```bash ../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.key` is empty (default in `dev` profile). - **Production:** Set `SMOA_API_KEY` (or `smoa.api.key`). Clients must send: - Header: `X-API-Key: `, or - Query: `?api_key=` ## 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: ` to receive 304 Not Modified when unchanged. Profiles: - **dev** – relaxed auth, H2 console at `/h2-console`, debug logging. - **prod** – set `SPRING_PROFILES_ACTIVE=prod` and `SMOA_API_KEY`, and switch datasource to PostgreSQL as needed. ## Database - **Default:** H2 file database at `./data/smoa`. **Flyway** runs migrations from `db/migration/`; `ddl-auto: update` in dev. - **Production:** Use PostgreSQL: set `spring.datasource.url=jdbc:postgresql://...`, `driver-class-name=org.postgresql.Driver`, and add `org.postgresql:postgresql` dependency. Use **`ddl-auto: validate`** (set in `application-prod.yml`) so Flyway owns the schema. ## Building ```bash cd backend ../gradlew build ``` JAR: ```bash ../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**): ```bash docker build -f backend/Dockerfile . docker run -p 8080:8080 ``` Sync and delete operations are **audit logged** (resource type, id, operation, success). ## Connecting the Android app 1. Point the app’s sync base URL to this backend (e.g. `http://:8080`). 2. 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 handles `conflict` / `remoteData` when present). 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](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).