mirror of
https://github.com/azaion/missions.git
synced 2026-06-21 20:31:06 +00:00
7025f4d075
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.
89 lines
4.3 KiB
Markdown
89 lines
4.3 KiB
Markdown
# 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<Waypoint> CreateWaypoint(Guid missionId, CreateWaypointRequest request);
|
|
Task<Waypoint> UpdateWaypoint(Guid missionId, Guid waypointId, UpdateWaypointRequest request);
|
|
Task<List<Waypoint>> 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.
|