refactor: enhance JWT authentication and CORS configuration

Updated JWT authentication to use configuration values instead of hardcoded secrets, improving security and flexibility. Enhanced CORS policy to conditionally allow origins based on configuration settings, with logging for permissive defaults. Updated README to reflect project renaming and clarify service context.
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-14 19:48:25 +03:00
parent 2fe394d732
commit 7025f4d075
74 changed files with 8494 additions and 19 deletions
@@ -0,0 +1,86 @@
# Flow F4 — Waypoint create / read / update / delete
> Post-rename / post-B7. Waypoint delete is a scoped variant of F3's cross-service cascade — same NO-transaction caveat applies (`architecture.md` ADR-006).
## Description
Waypoint CRUD nested under a mission (`/missions/{id}/waypoints/*`). Read-list is **unpaginated by spec**, ordered by `OrderNum`. **`UpdateWaypoint` is a full overwrite** of every field even though the request DTO looks "partial-shaped" (see `02_mission_planning` Caveats #2). Delete walks the cross-service cascade for **one** waypoint (compare F3 which walks for ALL waypoints of a mission).
## Preconditions
- Parent mission exists (`KeyNotFoundException``404` otherwise on every endpoint).
- Caller holds JWT with `permissions=FL` (F5).
- Schema in place for borrowed tables (`media`, `annotations`, `detection`) for delete.
## Sequence Diagram (DELETE one waypoint)
```mermaid
sequenceDiagram
autonumber
participant UI as Operator UI
participant Identity as 05_identity
participant Ctrl as MissionsController
participant WS as WaypointService
participant DB as 04_persistence (postgres-local)
participant Annot as [[annotations service schema]]
participant Det as [[detection pipeline schema]]
UI->>Identity: DELETE /missions/{id}/waypoints/{wpId} + JWT
Identity-->>Ctrl: authorized (policy "FL")
Ctrl->>WS: DeleteWaypoint(missionId, wpId)
WS->>DB: SELECT 1 FROM waypoints WHERE mission_id=? AND id=?
alt Not found
DB-->>WS: 0 rows
WS-->>UI: 404 Not Found
else Found
WS->>Annot: SELECT id FROM media WHERE waypoint_id = ? → mediaIds
WS->>Annot: SELECT id FROM annotations WHERE media_id IN ? → annotationIds
WS->>Det: DELETE FROM detection WHERE annotation_id IN annotationIds
WS->>Annot: DELETE FROM annotations WHERE id IN annotationIds
WS->>Annot: DELETE FROM media WHERE id IN mediaIds
WS->>DB: DELETE FROM waypoints WHERE id = ?
WS-->>UI: 204 No Content
end
```
## Flowchart (PUT — full overwrite)
```mermaid
flowchart TD
Start([PUT /missions/id/waypoints/wpId + body]) --> Auth{JWT + FL valid?}
Auth -->|no| Reject([401 / 403])
Auth -->|yes| Lookup[SELECT * FROM waypoints WHERE mission_id=? AND id=?]
Lookup --> Exists{Found?}
Exists -->|no| NotFound([404])
Exists -->|yes| Overwrite["UPDATE waypoints SET lat=?, lon=?, mgrs=?, alt=?, source=?, objective=?, order_num=?, name=? WHERE id=?"]
Overwrite --> Done([200 OK + Waypoint])
note1[NOTE: Sending partial body zeroes out missing numeric fields and resets enums to default 0. Spec uses Geopoint type with auto-conversion; code uses 3 flat fields. Carry-forward.]
```
## Data Flow
| Step | From | To | Data | Format |
|------|------|----|------|--------|
| 1 | UI | `MissionsController` (nested) | mission id + waypoint id (URL) + `CreateWaypointRequest` / `UpdateWaypointRequest` body | path params + JSON |
| 2 | `WaypointService` | `waypoints` table | INSERT / UPDATE / SELECT / DELETE | SQL |
| 3 | `WaypointService` | `media` / `annotations` / `detection` (delete only) | `SELECT id` then `DELETE WHERE id IN (...)` | SQL |
| 4 | `WaypointService` | UI | `Waypoint` entity / `List<Waypoint>` / `204` | JSON (PascalCase) |
## Error Scenarios
| Error | Where | Detection | Recovery |
|-------|-------|-----------|----------|
| Parent mission not found | Service entity lookup | `null` | `KeyNotFoundException``404` |
| Waypoint not found in mission | Service entity lookup with both ids | `null` | `KeyNotFoundException``404` |
| PUT zeroes out coordinates | `WaypointService.UpdateWaypoint` | None | Body intent is "partial" but code overwrites every column → silent data loss for missing fields. Carry-forward (`02_mission_planning` Caveats #2) |
| Race on N waypoints reordered as N PUTs | Caller-side | None | No reorder endpoint exists — caller must coordinate; partially-applied reorders leave inconsistent `order_num`s (`02_mission_planning` Caveats #5) |
| Delete cascade `relation does not exist` for `media` / `annotations` / `detection` | DELETE steps | Npgsql `PostgresException` (`42P01`) | `500`. Same diagnosis as F3: abnormal edge deployment |
| Partial failure mid-delete-cascade | Same as F3 | Npgsql exception | `500` + orphan rows. ADR-006 carry-forward |
## Performance Expectations
| Metric | Target | Notes |
|--------|--------|-------|
| Create / read / update | <10ms typical | Single round-trip |
| List (unpaginated) | <30ms typical for ≤1000 waypoints | `ix_waypoints_mission_id` index used; sort by `order_num` is in-memory (no order index) |
| Delete (with cross-service cascade) | <30ms typical for ≤100 media rows per waypoint | 56 round-trips |