mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-21 11:51:13 +00:00
9cfd80babe
AZ-495 (1 SP): formalize the modules-only documentation convention for the WebApi component. _docs/02_document/module-layout.md now carries an explicit Documentation Layout section anchoring WebApi docs at modules/api_program.md; the components/06_web_api/ folder is intentionally absent. .cursor/skills/new-task/SKILL.md Step 4 directs future agents at the correct path. Cycle-1 + cycle-2 F1 findings in the two batch-review files are marked RESOLVED with back-reference to AZ-495. Cycle-2 retrospective decision-item list F1 updated. AZ-496 (2 SP): bump Microsoft.AspNetCore.OpenApi and JwtBearer in SatelliteProvider.Api.csproj from 8.0.21 to 8.0.25, closing CVE- 2026-26130 (SignalR DoS - not reachable in this app, but the runtime patch is the recommended hardening per cycle-1 D1 + cycle-2 D3). SatelliteProvider.Tests.csproj has no direct JwtBearer reference - it consumes JwtBearer transitively via ProjectReference to Api, so no edit needed there. Dockerfiles use floating mcr.microsoft.com/ dotnet/aspnet:8.0 / sdk:8.0 / runtime:8.0 tags which auto-resolve to >= 8.0.25 on rebuild. Security artifacts (dependency_scan.md, security_report.md) and current-state docs (module-layout.md, architecture.md, modules/api_program.md, modules/tests_unit.md) updated to reflect 8.0.25. Batch report + code review report (verdict PASS_WITH_WARNINGS with 2 Low findings, neither blocking) written under _docs/03_implementation. Test suite gate deferred to Step 16 (Final Test Run) per implement skill convention. Patch-level bump within .NET 8 LTS; regression risk very low. Co-authored-by: Cursor <cursoragent@cursor.com>
104 lines
7.3 KiB
Markdown
104 lines
7.3 KiB
Markdown
# Module: Api/Program.cs
|
||
|
||
## Purpose
|
||
Application entry point. Configures DI container, sets up middleware, defines minimal API endpoints, runs database migrations on startup, and starts background services.
|
||
|
||
## Public Interface
|
||
|
||
### API Endpoints
|
||
| Method | Route | Handler | Description |
|
||
|--------|-------|---------|-------------|
|
||
| GET | `/tiles/{z}/{x}/{y}` | `ServeTile` | Slippy map tile server with in-memory caching |
|
||
| GET | `/api/satellite/tiles/latlon` | `GetTileByLatLon` | Download single tile by lat/lon/zoom |
|
||
| GET | `/api/satellite/tiles/mgrs` | `GetSatelliteTilesByMgrs` | MGRS stub (returns empty) |
|
||
| POST | `/api/satellite/upload` | `UploadUavTileBatch` | UAV tile batch upload (AZ-488) — multipart envelope, 5-rule quality gate, per-source UPSERT with `source='uav'`. Requires the `RequiresGpsPermission` policy. |
|
||
| POST | `/api/satellite/request` | `RequestRegion` | Queue region for async tile processing |
|
||
| GET | `/api/satellite/region/{id}` | `GetRegionStatus` | Get region processing status |
|
||
| POST | `/api/satellite/route` | `CreateRoute` | Create route with intermediate points |
|
||
| GET | `/api/satellite/route/{id}` | `GetRoute` | Get route with all points |
|
||
|
||
### Local Records (defined in Program.cs)
|
||
- `GetSatelliteTilesResponse`, `SatelliteTile` — MGRS response stubs
|
||
- `DownloadTileResponse` — tile download response
|
||
- `RequestRegionRequest` — region request body
|
||
- `ParameterDescriptionFilter` — Swagger operation filter
|
||
|
||
### Api/DTOs (AZ-488)
|
||
- `UavTileBatchUploadRequest` — multipart envelope with `metadata` (JSON string) and `files` (`IFormFileCollection`)
|
||
|
||
### Common/DTO (AZ-488)
|
||
- `UavTileMetadata`, `UavTileBatchMetadataPayload` — per-item metadata + envelope shape
|
||
- `UavTileBatchUploadResponse`, `UavTileUploadResultItem` — per-item response shape
|
||
- `UavTileUploadStatus`, `UavTileRejectReasons` — string-constant enumerations exposed in the v1.0.0 contract
|
||
|
||
## Internal Logic
|
||
|
||
### DI Registration
|
||
1. Serilog configured from `appsettings.json`
|
||
2. Connection string extracted from `ConnectionStrings:DefaultConnection`
|
||
3. Config bindings: `MapConfig`, `StorageConfig`, `ProcessingConfig`, `UavQualityConfig` (AZ-488)
|
||
4. **Request size limits (AZ-488)**: `KestrelServerOptions.Limits.MaxRequestBodySize` and `FormOptions.MultipartBodyLengthLimit` are set to `UavQualityConfig.MaxBatchSize × UavQualityConfig.MaxBytes` (default 100 × 5 MiB = 500 MiB) so an oversized UAV batch is rejected at the framework layer before reaching the handler.
|
||
5. Singletons: repositories (`TileRepository`, `RegionRepository`, `RouteRepository`), `GoogleMapsDownloaderV2`, `ITileService`, `IRegionService`, `IRouteService`, `IUavTileQualityGate`, `IUavTileUploadHandler` (AZ-488)
|
||
6. `IRegionRequestQueue` with configurable capacity
|
||
7. Hosted services: `RegionProcessingService`, `RouteProcessingService`
|
||
8. CORS policy: `TilesCors` — configured origins from `CorsConfig:AllowedOrigins`, falls back to allow-any
|
||
9. JSON options: camelCase, case-insensitive
|
||
10. **JWT authentication (AZ-487)**: `AddSatelliteJwt(builder.Configuration)` (extension in `SatelliteProvider.Api.Authentication`) registers `JwtBearer` with `TokenValidationParameters` set per the suite auth contract (signature + lifetime, no issuer/audience validation, 30 s clock skew, ≥ 32-byte HMAC key). Followed by `AddAuthorization` with the `RequiresGpsPermission` policy (AZ-488). The `PermissionsAuthorizationHandler` singleton supports both repeated-string and JSON-array shapes for the `permissions` claim.
|
||
|
||
### Startup
|
||
1. Database migration via `DatabaseMigrator.RunMigrations()` — throws on failure
|
||
2. Creates tiles and ready directories
|
||
3. Swagger enabled in Development mode
|
||
4. Middleware chain (order matters): `UseExceptionHandler` → `UseHttpsRedirection` → `UseCors("TilesCors")` → `UseAuthentication` → `UseAuthorization` → endpoint mapping.
|
||
5. Every `MapGet`/`MapPost` endpoint is decorated with `.RequireAuthorization()`; the framework returns 401 before the handler runs for any anonymous, expired, or invalid-signature request.
|
||
|
||
### ServeTile Handler
|
||
1. Checks `IMemoryCache` for tile bytes (1h absolute, 30min sliding expiration)
|
||
2. If cache miss: queries `ITileRepository.GetByTileCoordinatesAsync`
|
||
3. If no DB record: downloads tile via `GoogleMapsDownloaderV2.DownloadSingleTileAsync`, creates `TileEntity`, inserts
|
||
4. Returns image bytes with cache headers (`Cache-Control: public, max-age=86400`)
|
||
|
||
### GetTileByLatLon Handler
|
||
Downloads a tile, persists it, returns metadata as `DownloadTileResponse`.
|
||
|
||
### RequestRegion Handler
|
||
Validates size (100–10000m), delegates to `IRegionService.RequestRegionAsync`.
|
||
|
||
### UploadUavTileBatch Handler (AZ-488)
|
||
Buffers each `IFormFile` into memory, packages them as `UavUploadFile` records (filename, content-type, bytes), and delegates to `IUavTileUploadHandler.HandleAsync`. Envelope-level errors (mismatched batch, oversized batch, malformed metadata) are surfaced as HTTP 400 ProblemDetails; per-item rejects are returned in the HTTP 200 response payload. The endpoint is protected by `.RequireAuthorization(SatellitePermissions.UavUploadPolicy)` so 401 (no token) and 403 (no `GPS` permission) are returned before the handler runs.
|
||
|
||
## Dependencies
|
||
All project references: Common, DataAccess, Services.
|
||
NuGet: `Serilog.AspNetCore`, `Swashbuckle.AspNetCore`, `Microsoft.AspNetCore.OpenApi` (8.0.25, bumped from 8.0.21 by AZ-496), `Microsoft.AspNetCore.Authentication.JwtBearer` (8.0.25, added at 8.0.21 by AZ-487, bumped by AZ-496), `SixLabors.ImageSharp`, `Newtonsoft.Json`.
|
||
|
||
## Consumers
|
||
- HTTP clients (external)
|
||
- Integration tests (via HTTP)
|
||
|
||
## Data Models
|
||
Defines several local request/response records that are not shared with other projects.
|
||
|
||
## Configuration
|
||
All configuration sections are consumed here:
|
||
- `ConnectionStrings:DefaultConnection`
|
||
- `MapConfig`, `StorageConfig`, `ProcessingConfig`
|
||
- `UavQuality` (AZ-488) — `MinBytes`, `MaxBytes`, `MaxAgeDays`, `CapturedAtFutureSkewSeconds`, `MinLuminanceVariance`, `MaxBatchSize`, `LuminanceSampleSize`. Drives the 5-rule quality gate AND the per-request body-size limits.
|
||
- `CorsConfig:AllowedOrigins`
|
||
- `Jwt:Secret` — HMAC-SHA256 signing key for JWT validation (AZ-487). Resolution: `JWT_SECRET` env var (preferred, opaque production secret) → `Jwt:Secret` configuration key (`appsettings.Development.json` placeholder only). Startup fails fast if the resolved value is unset, empty, or shorter than 32 bytes.
|
||
- `Serilog` section
|
||
|
||
## External Integrations
|
||
- Google Maps (indirectly via `GoogleMapsDownloaderV2`)
|
||
- PostgreSQL (via repositories and DatabaseMigrator)
|
||
- File system (`./tiles/`, `./ready/`)
|
||
|
||
## Security
|
||
- CORS configured (permissive by default when no origins specified)
|
||
- Swagger only in Development; Bearer token "Authorize" button registered via `AddSecurityDefinition`/`AddSecurityRequirement` (AZ-487)
|
||
- HTTPS redirection enabled
|
||
- JWT bearer authentication (AZ-487) — every endpoint requires a valid HS256-signed token. Anonymous, expired, or signature-tampered requests return 401 before the handler runs.
|
||
- Permission-claim policies (AZ-488) — `POST /api/satellite/upload` is wrapped in `.RequireAuthorization(SatellitePermissions.UavUploadPolicy)`. The `PermissionsAuthorizationHandler` reads the `permissions` claim (repeated-string OR JSON-array shape) and returns 403 if `GPS` is not present.
|
||
|
||
## Tests
|
||
Integration tests exercise all endpoints. Unit test project has only a dummy test.
|