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.
5.6 KiB
Module: Azaion.Missions.Services.MissionService
File: Services/MissionService.cs
NOTE (forward-looking): post-rename + post-GPS-Denied-removal. Today's source is
Services/FlightService.csand its cascade still reaches intoorthophotos+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
public class MissionService(AppDataConnection db) {
Task<Mission> CreateMission(CreateMissionRequest request);
Task<Mission> UpdateMission(Guid id, UpdateMissionRequest request);
Task<Mission> GetMission(Guid id);
Task<PaginatedResponse<Mission>> GetMissions(GetMissionsQuery query);
Task DeleteMission(Guid id);
}
Internal Logic
CreateMission
- Existence check:
db.Vehicles.AnyAsync(v => v.Id == request.VehicleId). On miss -> throwArgumentException(mapped to 400 by middleware). - Build a fresh
Mission:Id = Guid.NewGuid()CreatedDate = request.CreatedDate ?? DateTime.UtcNowName,VehicleIdcopied from request.
db.InsertAsync(mission)and return.
UpdateMission
- Load by id (404 on miss).
- If
request.Name != null-> assign. - If
request.VehicleId.HasValue:- Existence check on the new
VehicleId(400 on miss). - Assign.
- Existence check on the new
db.UpdateAsync(mission).
GetMission
FirstOrDefaultAsync(m => m.Id == id)-> 404 on miss.
GetMissions -- paginated
- Build query on
db.Missions. - Filters:
Name: case-insensitiveContains(LOWER(name) LIKE %lower%).FromDate:created_date >= FromDate.ToDate:created_date <= ToDate.
- Compute
totalCountvia aCountAsync()call on the filtered query. - Fetch the page:
OrderByDescending(CreatedDate).Skip((Page-1)*PageSize).Take(PageSize).ToListAsync(). - Return
PaginatedResponse<Mission> { Items, TotalCount, Page, PageSize }.
DeleteMission -- manual cascade
- Load the mission (404 on miss).
- Top-level scrub:
map_objects WHERE mission_id = id->DeleteAsync.
- 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).
DELETE FROM waypoints WHERE mission_id = id.DELETE FROM missions WHERE id = id.
No transaction wraps the cascade -- partial failure leaves orphan rows.
Dependencies
LinqToDB(extension methods likeAnyAsync,DeleteAsync)AppDataConnection,Database.Entities.Mission+ 6 other entities used in the cascadeDTOs.{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
- Manual cascade vs. database
ON DELETE CASCADE: the DB declares plainREFERENCES(noON DELETEclause), 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. - 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. GetMissionsdoes a count + page query -- two round trips. Standard. The count is unfiltered by pagination but filtered by the same predicates.Namefilter and date filter are independent ANDs -- no support for OR or full-text search.- No soft-delete -- deletes are physical. Audit/recovery would need DB-level WAL or external backup.
UpdateMissionallows changingVehicleIdbut 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).