121 lines
5.5 KiB
Markdown
121 lines
5.5 KiB
Markdown
|
|
# 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: <key>`, or
|
|||
|
|
- Query: `?api_key=<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: <etag>` 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 <image-id>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
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://<host>: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).
|