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.
9.7 KiB
02 — Mission Planning (Missions + Waypoints + Cross-Service Cascade)
Spec source: ../../../suite/_docs/02_missions.md § "Missions" (items 1-9), ../../../suite/_docs/00_database_schema.md § Missions + Waypoints.
Required permission: FL.
Implementation status: ✅ implemented (with two divergences -- see Caveats).
NOTE (forward-looking): file paths, route prefixes, and identifiers below reflect the post-rename state. Today's source still uses
Flight*filenames +[Route("flights")]and the cascade still touchesorthophotos+gps_corrections. Renames + cascade shrink tracked under Jira AZ-EPIC children B6 (rename), B7 (GPS-Denied removal), B8 (HTTP routes).
Files (post-rename):
- HTTP:
Controllers/MissionsController.cs(parent + nested waypoint routes) - Services:
Services/MissionService.cs,Services/WaypointService.cs - DTOs:
DTOs/CreateMissionRequest.cs,DTOs/UpdateMissionRequest.cs,DTOs/GetMissionsQuery.cs,DTOs/CreateWaypointRequest.cs,DTOs/UpdateWaypointRequest.cs,DTOs/GeoPoint.cs - Resource enums:
Enums/WaypointSource.cs,Enums/WaypointObjective.cs
(Entity row maps live in 04_persistence.)
1. High-Level Overview
Purpose: Own the mission lifecycle for an edge deployment. A "mission" is a planned record with a name, creation timestamp, and assigned vehicle (Plane / Copter / UGV / GuidedMissile); "waypoints" are the ordered geo-points (with altitude, source, and objective) that define the mission's route. This component is consumed by autopilot (reads the mission and waypoints to drive the vehicle) and by the ui (map view + planning UI).
Architectural pattern: Aggregate root (Mission) with a sub-aggregate (Waypoint). Manual cascade-delete -- schema declares plain REFERENCES (no ON DELETE CASCADE); this service walks the dependency graph by hand.
Cross-service contract -- the cascade: when a mission or waypoint is deleted, this service also tears down rows in tables it does NOT own the schema for: media + annotations (owned by the annotations service) and detection (owned by the detection pipeline), plus its own map_objects. Per ../../../suite/_docs/02_missions.md §5 + §9 this is the canonical, spec-defined behavior -- this service is the only place that knows the full mission ownership graph and is contractually responsible for this cleanup. The shared local PostgreSQL on the edge device makes the multi-table cascade physically possible in one connection.
Removed from cascade in B7: orthophotos and gps_corrections. Those tables now live in the separate gps-denied service per ../../../suite/_docs/11_gps_denied.md. MissionService.DeleteMission and WaypointService.DeleteWaypoint no longer reference them.
Upstream dependencies: 05_identity ([Authorize FL]), 04_persistence, 06_http_conventions, 01_vehicle_catalog (existence check on vehicle_id).
Downstream consumers (live runtime): autopilot (reads missions + waypoints), ui (planning + map). The new gps-denied service references mission_id and waypoint_id from its own tables but does NOT depend on this service at runtime; cleanup of its rows is its own concern.
2. Internal Interface
public class MissionService(AppDataConnection db) {
Task<Mission> CreateMission(CreateMissionRequest);
Task<Mission> UpdateMission(Guid id, UpdateMissionRequest);
Task<Mission> GetMission(Guid id);
Task<PaginatedResponse<Mission>> GetMissions(GetMissionsQuery);
Task DeleteMission(Guid id); // cross-service cascade
}
public class WaypointService(AppDataConnection db) {
Task<Waypoint> CreateWaypoint(Guid missionId, CreateWaypointRequest);
Task<Waypoint> UpdateWaypoint(Guid missionId, Guid waypointId, UpdateWaypointRequest);
Task<List<Waypoint>> GetWaypoints(Guid missionId); // unpaginated by spec
Task DeleteWaypoint(Guid missionId, Guid waypointId); // cross-service cascade
}
Throws KeyNotFoundException (-> 404), ArgumentException (-> 400, when referenced vehicle_id doesn't exist).
3. External API
Missions
| Spec # | Endpoint | Method | Auth | Description |
|---|---|---|---|---|
| 1 | /missions |
POST | FL |
Create. Body CreateMissionRequest. Code throws ArgumentException -> 400 if VehicleId doesn't exist; spec says 404 -- minor divergence. |
| 2 | /missions/{id:guid} |
PUT | FL |
Partial update (Name and/or VehicleId). |
| 7 | /missions/{id:guid} |
GET | FL |
Single by id. |
| 8 | /missions |
GET | FL |
Paginated list. Query: Name?, FromDate?, ToDate?, Page=1, PageSize=20. Returns PaginatedResponse<Mission> (envelope from 06_http_conventions). |
| 9 | /missions/{id:guid} |
DELETE | FL |
Cascade-deletes waypoints, media, annotations, detection, map_objects (in dependency order). |
Waypoints (nested under mission)
| Spec # | Endpoint | Method | Auth | Description |
|---|---|---|---|---|
| 3 | /missions/{id:guid}/waypoints |
POST | FL |
Create. Body CreateWaypointRequest. 404 if mission missing. |
| 4 | /missions/{id:guid}/waypoints/{wpId:guid} |
PUT | FL |
Full overwrite of all waypoint fields (Caveats #2 -- diverges from partial-update intent). |
| 5 | /missions/{id:guid}/waypoints/{wpId:guid} |
DELETE | FL |
Cascade-deletes related media, annotations, detection. |
| 6 | /missions/{id:guid}/waypoints |
GET | FL |
Ordered by OrderNum. Unpaginated (matches spec). |
Wire shape: PascalCase (suite-wide divergence -- see 06_http_conventions).
4. Data Access Patterns
Read queries
| Query | Frequency | Hot Path | Index |
|---|---|---|---|
missions WHERE id = ? |
Every read/update/delete | Yes | PK ✓ |
missions WHERE ... ORDER BY created_date DESC LIMIT N OFFSET M (+ count) |
Listing | Yes | None on created_date -- could be added |
vehicles WHERE id = ? (existence) |
Every mission create / update with vehicle change | Yes | PK ✓ (cross-component) |
waypoints WHERE mission_id = ? AND id = ? |
Per-waypoint read/update/delete | Yes | PK + ix_waypoints_mission_id ✓ |
waypoints WHERE mission_id = ? ORDER BY order_num |
Nested list | Medium | ix_waypoints_mission_id (sort still in-memory) |
Cascade-delete writes (MissionService.DeleteMission)
In strict dependency order:
DELETE FROM map_objects WHERE mission_id = ?(autopilot-written, owned-here schema)- Resolve
waypointIds = SELECT id FROM waypoints WHERE mission_id = ? - If any: resolve
mediaIds,annotationIds, thenDELETE FROM detection,DELETE FROM annotations,DELETE FROM media(cross-service tables -- schema owned by annotations + detection pipeline) DELETE FROM waypoints WHERE mission_id = ?DELETE FROM missions WHERE id = ?
WaypointService.DeleteWaypoint does the equivalent steps 2-4 scoped to one waypoint.
No transaction wraps either cascade -- partial failure leaves orphan rows. Tracked as Caveat #1; would be a one-line fix (db.BeginTransactionAsync()).
Caching
None.
Storage Estimates
Not specified.
5. Implementation Details
State Management: Stateless services.
Key business rules:
mission.vehicle_idmust reference an existing vehicle (validated on create + on update if changed).waypoint.mission_idmust reference an existing mission (validated on create).- Cascade tables must be deleted in child-before-parent order due to FK constraints.
Error Handling: Services throw; 06_http_conventions middleware maps.
6. Extensions and Helpers
PaginatedResponse<T> (defined in 06_http_conventions) is consumed only by this component.
7. Caveats & Edge Cases
- No transaction around cascade delete -- partial failure orphans rows in
media,annotations,detection,map_objects, orwaypoints. Wrapping indb.BeginTransactionAsync()is one extra line and would make the cascade atomic. UpdateWaypointoverwrites all fields even though the request looks "partial-shaped" -- sending{}zeroes out coordinates and resets enums. Spec §4 also overwrites all fields, but spec uses the auto-convertingGeopointtype so a missingGeopointwould benullnot zero. With code's 3-flat-fields shape, this is more error-prone.- Geopoint shape divergence from spec: spec defines a single
string GPSwith auto-conversion (Lat <-> MGRS). Code uses 3 separate columns with no conversion. Carries throughWaypoint,MapObject, and the request DTOs. - Vehicle existence check + mission insert is non-transactional -- TOCTOU window for vehicle delete is mitigated by the FK (which would reject the insert), but the error UX would surface as a 500 instead of a 400 in that race.
- No reorder endpoint -- N waypoints reordered = N PUTs, racy.
- Cascade depends on cross-service tables existing in the same DB. In standard edge deployment this is guaranteed (annotations/detection migrate them in the same compose stack, same
postgres-local). In any deployment where those services are absent, the cascade will throwrelation does not exist. - Entity returned on the wire with
[Association]properties (Mission.Vehicle,Mission.Waypoints,Waypoint.Mission); LinqToDB does NOT eager-load by default onFirstOrDefaultAsync(predicate), so they serialize asnull/[]. Verify in Step 4 against actual responses. - Spec §1 says 404 on missing VehicleId; code throws
ArgumentExceptionwhich maps to 400. Minor divergence.
8. Dependency Graph
Must be implemented after: 05_identity, 06_http_conventions, 04_persistence, 01_vehicle_catalog.
Blocks: 07_host.
9. Logging Strategy
No app-level logs.