# Module: `Azaion.Missions.Services.WaypointService` **File**: `Services/WaypointService.cs` > **NOTE (forward-looking)**: post-rename + post-GPS-Denied-removal. Today's source still uses `Flight`/`flightId` and the cascade reaches into `gps_corrections`. Renames tracked under Jira AZ-EPIC child B6; cascade shrink under B7. ## Purpose CRUD over waypoints (sub-aggregate of `Mission`) plus a manual cascade-delete chain that walks `waypoint -> media -> annotations -> detection`. All operations are scoped by `missionId` -- no cross-mission waypoint addressing. ## Public Interface ```csharp public class WaypointService(AppDataConnection db) { Task CreateWaypoint(Guid missionId, CreateWaypointRequest request); Task UpdateWaypoint(Guid missionId, Guid waypointId, UpdateWaypointRequest request); Task> GetWaypoints(Guid missionId); Task DeleteWaypoint(Guid missionId, Guid waypointId); } ``` ## Internal Logic ### `CreateWaypoint` 1. Existence check: `db.Missions.AnyAsync(m => m.Id == missionId)` -> `KeyNotFoundException` ("Mission not found") on miss -> 404. 2. Build a fresh `Waypoint`: - `Id = Guid.NewGuid()`, `MissionId = missionId`. - `Lat`, `Lon`, `Mgrs` from `request.GeoPoint?` (all three null-passthrough; if `GeoPoint` is null, all three are null). - `WaypointSource`, `WaypointObjective` copied as-is. - `OrderNum`, `Height` copied as-is. 3. `db.InsertAsync(waypoint)`. ### `UpdateWaypoint` 1. Load by composite predicate `w.MissionId == missionId && w.Id == waypointId` -> 404 on miss (so updates with the wrong `missionId` get 404, not 403). 2. **Full overwrite** -- every field is set unconditionally from the request, including `null`-passthrough on `Lat/Lon/Mgrs`. There is no partial-update semantic here, unlike `UpdateVehicleRequest`. 3. `db.UpdateAsync(waypoint)`. ### `GetWaypoints` - `db.Waypoints.Where(w.MissionId == missionId).OrderBy(w.OrderNum).ToListAsync()` -- no pagination. ### `DeleteWaypoint` -- manual cascade 1. Load by composite (404 on miss). 2. `mediaIds = SELECT id FROM media WHERE waypoint_id = waypointId`. 3. If any media exist: - `annotationIds = SELECT id FROM annotations WHERE media_id IN (mediaIds)`. - If any annotations exist: `DELETE FROM detection WHERE annotation_id IN (annotationIds)`. - `DELETE FROM annotations WHERE media_id IN (mediaIds)`. 4. `DELETE FROM media WHERE waypoint_id = waypointId`. 5. `DELETE FROM waypoints WHERE id = waypointId`. **Removed from cascade in B7**: `gps_corrections WHERE waypoint_id = waypointId`. Schema gone after B9. **No transaction** wraps the cascade either. ## Dependencies - `AppDataConnection`, entities `Waypoint`, `Mission` (existence check), and 3 dependent entities used in the cascade. - `DTOs.GeoPoint`, `DTOs.CreateWaypointRequest`, `DTOs.UpdateWaypointRequest`. - `Azaion.Missions.Enums` (`WaypointSource`, `WaypointObjective` -- typed properties). ## Consumers - `Controllers.MissionsController` -- exposed under `missions/{id}/waypoints/*` routes. ## Data Models Reads `missions` (existence). Writes `waypoints`. Cascade also writes `media`, `annotations`, `detection`. ## Configuration / External Integrations None. ## Security - Behind `[Authorize(Policy = "FL")]` via the controller. - Composite-key load (`MissionId AND Id`) means a user cannot operate on a waypoint by guessing only its id -- they must also know the mission id (defense in depth, though both are GUIDs). ## Tests None present. ## Notes / Smells 1. **`UpdateWaypoint` does a full overwrite** even though `UpdateWaypointRequest` is "partial-shaped". Any client sending `{}` would silently zero out `Lat/Lon/Mgrs/OrderNum/Height` and reset enums to `0`. Client contract is fragile. 2. **`waypoint_id` not found vs. `mission_id`/`waypoint_id` mismatch** both return 404 with the same message -- slight UX issue (you can't tell which id is wrong). 3. **No reorder endpoint** -- `OrderNum` is set in the request but there's no atomic "reorder this list" operation. Reordering N waypoints requires N PUTs and is racy. 4. **Same transaction gap** as `MissionService.DeleteMission` -- no `BeginTransactionAsync` around the cascade. 5. **`GeoPoint?` swallowing** -- sending a request with `GeoPoint = null` clears the location entirely. Likely intentional but combined with "no validation" means an empty waypoint can be created.