Files
missions/_docs/02_document/modules/entities.md
T
Oleksandr Bezdieniezhnykh 7025f4d075 refactor: enhance JWT authentication and CORS configuration
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.
2026-05-14 19:48:25 +03:00

7.8 KiB

Module: Azaion.Missions.Database.Entities

Files (7): Vehicle.cs, Mission.cs, Waypoint.cs, MapObject.cs, Media.cs, Annotation.cs, Detection.cs

NOTE (forward-looking): this doc reflects the post-rename state. Today's source still has Aircraft.cs, Flight.cs, Orthophoto.cs, GpsCorrection.cs. The renames + GPS-Denied removal are tracked under Jira AZ-EPIC children B6 (domain rename) and B7 (GPS-Denied removal).

Purpose

LinqToDB row-mapping classes. Each entity uses [Table("snake_case_name")] + [Column("snake_case")] + [PrimaryKey] to map to a PostgreSQL table. Two entities (Mission, Waypoint) include [Association]-based navigation; the rest are flat row maps.

Public Interface

Vehicle (table vehicles)

[Table("vehicles")]
public class Vehicle {
    [PrimaryKey, Column("id")]                      public Guid Id;
    [Column("type")]                                 public VehicleType Type;       // Plane | Copter | UGV | GuidedMissile
    [Column("model")]                                public string Model = "";
    [Column("name")]                                 public string Name = "";
    [Column("fuel_type")]                            public FuelType FuelType;
    [Column("battery_capacity")]                     public decimal BatteryCapacity;
    [Column("engine_consumption")]                   public decimal EngineConsumption;
    [Column("engine_consumption_idle")]              public decimal EngineConsumptionIdle;
    [Column("is_default")]                           public bool IsDefault;
}

Mission (table missions)

[Table("missions")]
public class Mission {
    [PrimaryKey, Column("id")]                        public Guid Id;
    [Column("created_date")]                          public DateTime CreatedDate;
    [Column("name")]                                  public string Name = "";
    [Column("vehicle_id")]                            public Guid VehicleId;

    [Association(ThisKey=VehicleId, OtherKey=Vehicle.Id)]   public Vehicle? Vehicle;
    [Association(ThisKey=Id,        OtherKey=Waypoint.MissionId)] public List<Waypoint> Waypoints = [];
}

Waypoint (table waypoints)

[Table("waypoints")]
public class Waypoint {
    [PrimaryKey, Column("id")]                        public Guid Id;
    [Column("mission_id")]                            public Guid MissionId;
    [Column("lat")]                                   public decimal? Lat;
    [Column("lon")]                                   public decimal? Lon;
    [Column("mgrs")]                                  public string?  Mgrs;
    [Column("waypoint_source")]                       public WaypointSource    WaypointSource;
    [Column("waypoint_objective")]                    public WaypointObjective WaypointObjective;
    [Column("order_num")]                             public int     OrderNum;
    [Column("height")]                                public decimal Height;

    [Association(ThisKey=MissionId, OtherKey=Mission.Id)] public Mission? Mission;
}

MapObject (table map_objects)

[Table("map_objects")]
public class MapObject {
    [PrimaryKey, Column("id")]    public Guid Id;
    [Column("mission_id")]        public Guid MissionId;
    [Column("h3_index")]          public string H3Index = "";  // Uber H3 hex grid
    [Column("mgrs")]              public string Mgrs    = "";
    [Column("lat")]               public decimal? Lat;
    [Column("lon")]               public decimal? Lon;
    [Column("class_num")]         public int     ClassNum;
    [Column("label")]             public string  Label = "";
    [Column("size_width_m")]      public decimal SizeWidthM;
    [Column("size_length_m")]     public decimal SizeLengthM;
    [Column("confidence")]        public decimal Confidence;
    [Column("object_status")]     public ObjectStatus ObjectStatus;
    [Column("first_seen_at")]     public DateTime FirstSeenAt;
    [Column("last_seen_at")]      public DateTime LastSeenAt;
}

Media / Annotation / Detection (cross-service stubs -- see "Notes / Smells")

[Table("media")]
public class Media {
    [PrimaryKey, Column("id")]   public string Id = "";    // TEXT primary key
    [Column("waypoint_id")]      public Guid?  WaypointId;
}

[Table("annotations")]
public class Annotation {
    [PrimaryKey, Column("id")]   public string Id       = "";   // TEXT
    [Column("media_id")]         public string MediaId  = "";   // TEXT FK to media.id
}

[Table("detection")]   // SINGULAR table name -- diverges from every other entity (owned by detection pipeline)
public class Detection {
    [PrimaryKey, Column("id")]   public Guid   Id;
    [Column("annotation_id")]    public string AnnotationId = "";
}

Internal Logic

Pure POCOs. The only behavior comes from LinqToDB attribute mapping ([Table], [Column], [PrimaryKey], [Association]).

Dependencies

  • LinqToDB.Mapping (NuGet)
  • Azaion.Missions.Enums (for Vehicle, Waypoint, MapObject)

Consumers

  • Database.AppDataConnection -- exposes ITable<T> for each entity.
  • Database.DatabaseMigrator -- implicitly (defines DDL for the same names; does NOT reference entity types).
  • Services.VehicleService, Services.MissionService, Services.WaypointService -- entity types are returned/constructed.

Data Model Highlights

vehicles --< missions --< waypoints --? media --< annotations --< detection
                       \--< map_objects
  • Mission is the central aggregate root: most domain rows hang off mission_id.
  • Waypoint is a sub-aggregate of Mission and is the join point for Media.
  • MapObject is detection output (class_num + confidence + spatial index) tied to a mission, NOT to a specific waypoint. Schema is owned by this service, but rows are written by autopilot (per ../../suite/_docs/06_autopilot_design.md).

Cross-service stubs

Media, Annotation, Detection are intentional read-only stubs -- only Id and one foreign key each. They're queried/deleted by MissionService.DeleteMission and WaypointService.DeleteWaypoint but never written by this service. Schema-wise they are owned by other suite components (annotations for media + annotations, the detection pipeline for detection), per ../../suite/_docs/00_top_level_architecture.md and ../../suite/_docs/01_annotations.md. The shared edge-PostgreSQL pattern means each service migrates its own tables but all services see the full schema.

These three are deliberately NOT in DatabaseMigrator.Sql -- their schema is created by the owning services on the same shared local PostgreSQL.

Configuration / External Integrations / Security

None directly. Persistence is delegated to LinqToDB + Npgsql.

Tests

None present.

Notes / Smells

  1. Detection table singular while vehicles, missions, waypoints, map_objects, media, annotations are plural. Owned by another service -- naming is THEIR call to make consistent.
  2. Mixed PK types: Vehicle, Mission, Waypoint, MapObject, Detection use Guid; Media, Annotation use string (TEXT, XxHash64-based per ../../suite/_docs/00_database_schema.md).
  3. No domain methods -- all business rules (e.g., the "default vehicle" exclusivity in VehicleService) live in services, not in the entities. Consistent and intentional for a thin-data-model approach.
  4. Media.WaypointId is nullable while every other foreign key here is not. Suggests Media can attach to a non-waypoint context (e.g., mission-level media); enforcement is on annotations's side.
  5. Geopoint divergence (carry to verification log): spec stores Waypoints.GPS as a single string GPS field with Lat <-> MGRS auto-conversion (per ../../suite/_docs/02_missions.md and ../../suite/_docs/00_database_schema.md). Code splits it into 3 separate columns. Resolution lives outside the GPS-Denied removal scope -- carry forward.