mirror of
https://github.com/azaion/missions.git
synced 2026-06-21 14:41: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.
113 lines
5.6 KiB
Markdown
113 lines
5.6 KiB
Markdown
# 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<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`
|
|
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<Mission> { 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).
|