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>
7.3 KiB
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 stubsDownloadTileResponse— tile download responseRequestRegionRequest— region request bodyParameterDescriptionFilter— Swagger operation filter
Api/DTOs (AZ-488)
UavTileBatchUploadRequest— multipart envelope withmetadata(JSON string) andfiles(IFormFileCollection)
Common/DTO (AZ-488)
UavTileMetadata,UavTileBatchMetadataPayload— per-item metadata + envelope shapeUavTileBatchUploadResponse,UavTileUploadResultItem— per-item response shapeUavTileUploadStatus,UavTileRejectReasons— string-constant enumerations exposed in the v1.0.0 contract
Internal Logic
DI Registration
- Serilog configured from
appsettings.json - Connection string extracted from
ConnectionStrings:DefaultConnection - Config bindings:
MapConfig,StorageConfig,ProcessingConfig,UavQualityConfig(AZ-488) - Request size limits (AZ-488):
KestrelServerOptions.Limits.MaxRequestBodySizeandFormOptions.MultipartBodyLengthLimitare set toUavQualityConfig.MaxBatchSize × UavQualityConfig.MaxBytes(default 100 × 5 MiB = 500 MiB) so an oversized UAV batch is rejected at the framework layer before reaching the handler. - Singletons: repositories (
TileRepository,RegionRepository,RouteRepository),GoogleMapsDownloaderV2,ITileService,IRegionService,IRouteService,IUavTileQualityGate,IUavTileUploadHandler(AZ-488) IRegionRequestQueuewith configurable capacity- Hosted services:
RegionProcessingService,RouteProcessingService - CORS policy:
TilesCors— configured origins fromCorsConfig:AllowedOrigins, falls back to allow-any - JSON options: camelCase, case-insensitive
- JWT authentication (AZ-487):
AddSatelliteJwt(builder.Configuration)(extension inSatelliteProvider.Api.Authentication) registersJwtBearerwithTokenValidationParametersset per the suite auth contract (signature + lifetime, no issuer/audience validation, 30 s clock skew, ≥ 32-byte HMAC key). Followed byAddAuthorizationwith theRequiresGpsPermissionpolicy (AZ-488). ThePermissionsAuthorizationHandlersingleton supports both repeated-string and JSON-array shapes for thepermissionsclaim.
Startup
- Database migration via
DatabaseMigrator.RunMigrations()— throws on failure - Creates tiles and ready directories
- Swagger enabled in Development mode
- Middleware chain (order matters):
UseExceptionHandler→UseHttpsRedirection→UseCors("TilesCors")→UseAuthentication→UseAuthorization→ endpoint mapping. - Every
MapGet/MapPostendpoint is decorated with.RequireAuthorization(); the framework returns 401 before the handler runs for any anonymous, expired, or invalid-signature request.
ServeTile Handler
- Checks
IMemoryCachefor tile bytes (1h absolute, 30min sliding expiration) - If cache miss: queries
ITileRepository.GetByTileCoordinatesAsync - If no DB record: downloads tile via
GoogleMapsDownloaderV2.DownloadSingleTileAsync, createsTileEntity, inserts - 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:DefaultConnectionMapConfig,StorageConfig,ProcessingConfigUavQuality(AZ-488) —MinBytes,MaxBytes,MaxAgeDays,CapturedAtFutureSkewSeconds,MinLuminanceVariance,MaxBatchSize,LuminanceSampleSize. Drives the 5-rule quality gate AND the per-request body-size limits.CorsConfig:AllowedOriginsJwt:Secret— HMAC-SHA256 signing key for JWT validation (AZ-487). Resolution:JWT_SECRETenv var (preferred, opaque production secret) →Jwt:Secretconfiguration key (appsettings.Development.jsonplaceholder only). Startup fails fast if the resolved value is unset, empty, or shorter than 32 bytes.Serilogsection
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/uploadis wrapped in.RequireAuthorization(SatellitePermissions.UavUploadPolicy). ThePermissionsAuthorizationHandlerreads thepermissionsclaim (repeated-string OR JSON-array shape) and returns 403 ifGPSis not present.
Tests
Integration tests exercise all endpoints. Unit test project has only a dummy test.