# Module: `Azaion.Missions.Services.MissionService` **File**: `Services/MissionService.cs` > **NOTE (forward-looking)**: post-rename + post-GPS-Denied-removal. Today's source is `Services/FlightService.cs` and its cascade still reaches into `orthophotos` + `gps_corrections`. Renames tracked under Jira AZ-EPIC child B6; cascade shrink under B7. ## Purpose CRUD over the `missions` aggregate plus a manual cascade-delete chain that walks `mission -> waypoints -> media -> annotations -> detection` and clears the `map_objects` side table. ## Public Interface ```csharp public class MissionService(AppDataConnection db) { Task CreateMission(CreateMissionRequest request); Task UpdateMission(Guid id, UpdateMissionRequest request); Task GetMission(Guid id); Task> GetMissions(GetMissionsQuery query); Task DeleteMission(Guid id); } ``` ## Internal Logic ### `CreateMission` 1. Existence check: `db.Vehicles.AnyAsync(v => v.Id == request.VehicleId)`. On miss -> throw `ArgumentException` (mapped to 400 by middleware). 2. Build a fresh `Mission`: - `Id = Guid.NewGuid()` - `CreatedDate = request.CreatedDate ?? DateTime.UtcNow` - `Name`, `VehicleId` copied from request. 3. `db.InsertAsync(mission)` and return. ### `UpdateMission` 1. Load by id (404 on miss). 2. If `request.Name != null` -> assign. 3. If `request.VehicleId.HasValue`: - Existence check on the new `VehicleId` (400 on miss). - Assign. 4. `db.UpdateAsync(mission)`. ### `GetMission` - `FirstOrDefaultAsync(m => m.Id == id)` -> 404 on miss. ### `GetMissions` -- paginated 1. Build query on `db.Missions`. 2. Filters: - `Name`: case-insensitive `Contains` (`LOWER(name) LIKE %lower%`). - `FromDate`: `created_date >= FromDate`. - `ToDate`: `created_date <= ToDate`. 3. Compute `totalCount` via a `CountAsync()` call on the filtered query. 4. Fetch the page: `OrderByDescending(CreatedDate).Skip((Page-1)*PageSize).Take(PageSize).ToListAsync()`. 5. Return `PaginatedResponse { Items, TotalCount, Page, PageSize }`. ### `DeleteMission` -- manual cascade 1. Load the mission (404 on miss). 2. Top-level scrub: - `map_objects WHERE mission_id = id` -> `DeleteAsync`. 3. Resolve dependents through waypoints: - `waypointIds = SELECT id FROM waypoints WHERE mission_id = id`. - If any waypoints exist: - `mediaIds = SELECT id FROM media WHERE waypoint_id IN (waypointIds)`. - 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)`. - `DELETE FROM media WHERE waypoint_id IN (waypointIds)`. 4. `DELETE FROM waypoints WHERE mission_id = id`. 5. `DELETE FROM missions WHERE id = id`. **No transaction wraps the cascade** -- partial failure leaves orphan rows. ## Dependencies - `LinqToDB` (extension methods like `AnyAsync`, `DeleteAsync`) - `AppDataConnection`, `Database.Entities.Mission` + 6 other entities used in the cascade - `DTOs.{CreateMissionRequest, UpdateMissionRequest, GetMissionsQuery, PaginatedResponse}` ## Consumers - `Controllers.MissionsController` -- wraps each method 1:1. ## Data Models Reads `vehicles` (existence check). Writes `missions`. On delete, also writes `map_objects`, `media`, `annotations`, `detection`, `waypoints`. ## Cross-service contract `media` / `annotations` / `detection` are owned schema-wise by other edge components (`annotations`, detection pipeline) on the shared local PostgreSQL. `MissionService.DeleteMission` is the canonical place that knows the full mission ownership graph: when a mission is deleted, its derived rows in those tables are scrubbed by THIS service. If those tables are missing on the deployment target, the cascade throws `relation does not exist`; in normal edge deployment they always exist (annotations migrates them at startup, same DB). **Removed from cascade in B7**: `orthophotos`, `gps_corrections`. Those tables are no longer in this DB after B9; if a fielded device still has them they'll just be dropped (cascade no longer touches them). ## Configuration / External Integrations None. ## Security - Behind `[Authorize(Policy = "FL")]` (controller-level). - Manual cascade respects FK direction; no SQL injection surface. ## Tests None present. ## Notes / Smells 1. **Manual cascade vs. database `ON DELETE CASCADE`**: the DB declares plain `REFERENCES` (no `ON DELETE` clause), so the service does the work. Deliberate (likely to permit partial denial / business rules), but it means schema and code are coupled -- any new mission-owned table must be added BOTH to the schema and to this method. 2. **No transaction** around the cascade -- failure mid-cascade orphans rows. For PostgreSQL, wrapping in `db.BeginTransactionAsync()` is one extra line and would make the cascade atomic. Opportunistic improvement candidate. 3. **`GetMissions` does a count + page query** -- two round trips. Standard. The count is unfiltered by pagination but filtered by the same predicates. 4. **`Name` filter and date filter are independent ANDs** -- no support for OR or full-text search. 5. **No soft-delete** -- deletes are physical. Audit/recovery would need DB-level WAL or external backup. 6. **`UpdateMission` allows changing `VehicleId`** but does NOT validate that any in-mission state (waypoints, etc.) is compatible with the new vehicle. Probably fine if vehicle-specific behavior is downstream (e.g., autopilot recomputes route on next read).