using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc; using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; using SatelliteProvider.DataAccess; using SatelliteProvider.DataAccess.Repositories; using SatelliteProvider.Common.Configs; using SatelliteProvider.Common.Interfaces; using SatelliteProvider.Services; var builder = WebApplication.CreateBuilder(args); var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); builder.Services.Configure(builder.Configuration.GetSection("MapConfig")); builder.Services.Configure(builder.Configuration.GetSection("StorageConfig")); builder.Services.Configure(builder.Configuration.GetSection("ProcessingConfig")); builder.Services.AddSingleton(sp => new TileRepository(connectionString)); builder.Services.AddSingleton(sp => new RegionRepository(connectionString)); builder.Services.AddHttpClient(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "Satellite Provider API", Version = "v1" }); c.MapType(() => new OpenApiSchema { Type = "object", Properties = new Dictionary { ["timestamp"] = new() { Type = "string", Format = "date-time", Description = "Image capture timestamp" }, ["image"] = new() { Type = "string", Format = "binary", Description = "Image file to upload" }, ["lat"] = new() { Type = "number", Format = "double", Description = "Latitude coordinate where image was captured" }, ["lon"] = new() { Type = "number", Format = "double", Description = "Longitude coordinate where image was captured" }, ["height"] = new() { Type = "number", Format = "double", Description = "Height/altitude in meters where image was captured" }, ["focalLength"] = new() { Type = "number", Format = "double", Description = "Camera focal length in millimeters" }, ["sensorWidth"] = new() { Type = "number", Format = "double", Description = "Camera sensor width in millimeters" }, ["sensorHeight"] = new() { Type = "number", Format = "double", Description = "Camera sensor height in millimeters" } }, Required = new HashSet { "timestamp", "image", "lat", "lon", "height", "focalLength", "sensorWidth", "sensorHeight" } }); c.OperationFilter(); }); var app = builder.Build(); var logger = app.Services.GetRequiredService>(); var migrator = new DatabaseMigrator(connectionString, logger as ILogger); if (!migrator.RunMigrations()) { throw new Exception("Database migration failed. Application cannot start."); } if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.MapGet("/api/satellite/tiles/latlon", GetSatelliteTilesByLatLon) .WithOpenApi(op => new(op) { Summary = "Get satellite tiles by latitude and longitude coordinates" }); app.MapGet("/api/satellite/tiles/mgrs", GetSatelliteTilesByMgrs) .WithOpenApi(op => new(op) { Summary = "Get satellite tiles by MGRS coordinates" }); app.MapPost("/api/satellite/upload", UploadImage) .Accepts("multipart/form-data") .WithOpenApi(op => new(op) { Summary = "Upload image with metadata and save to /maps folder" }) .DisableAntiforgery(); app.MapPost("/api/satellite/tiles/download", DownloadSingleTile) .WithOpenApi(op => new(op) { Summary = "TEMPORARY: Download single tile at specified coordinates" }); app.Run(); IResult GetSatelliteTilesByLatLon(double lat, double lon, double squareSideMeters) { return Results.Ok(new GetSatelliteTilesResponse()); } IResult GetSatelliteTilesByMgrs(string mgrs, double squareSideMeters) { return Results.Ok(new GetSatelliteTilesResponse()); } IResult UploadImage([FromForm] UploadImageRequest request) { return Results.Ok(new SaveResult { Success = false }); } async Task DownloadSingleTile([FromBody] DownloadTileRequest request, ITileService tileService, ILogger logger) { try { logger.LogInformation("Downloading single tile at ({Lat}, {Lon}) with zoom level {Zoom}", request.Latitude, request.Longitude, request.ZoomLevel); var tiles = await tileService.DownloadAndStoreTilesAsync( request.Latitude, request.Longitude, 1.0, request.ZoomLevel); if (tiles.Count == 0) { logger.LogWarning("No tiles were downloaded"); return Results.NotFound(new { message = "No tiles were downloaded" }); } var tile = tiles[0]; logger.LogInformation("Tile downloaded successfully: {Id}", tile.Id); var response = new DownloadTileResponse { Id = tile.Id, ZoomLevel = tile.ZoomLevel, Latitude = tile.Latitude, Longitude = tile.Longitude, TileSizeMeters = tile.TileSizeMeters, TileSizePixels = tile.TileSizePixels, ImageType = tile.ImageType, MapsVersion = tile.MapsVersion, FilePath = tile.FilePath, CreatedAt = tile.CreatedAt, UpdatedAt = tile.UpdatedAt }; return Results.Ok(response); } catch (Exception ex) { logger.LogError(ex, "Failed to download tile"); return Results.Problem(detail: ex.Message, statusCode: 500); } } public record GetSatelliteTilesResponse { public List Tiles { get; set; } = new(); } public record SatelliteTile { public string TileId { get; set; } = string.Empty; public byte[] ImageData { get; set; } = Array.Empty(); public double Lat { get; set; } public double Lon { get; set; } public int ZoomLevel { get; set; } } public record UploadImageRequest { [Required] public DateTime Timestamp { get; set; } [Required] public IFormFile? Image { get; set; } [Required] public double Lat { get; set; } [Required] public double Lon { get; set; } [Required] public double Height { get; set; } [Required] public double FocalLength { get; set; } [Required] public double SensorWidth { get; set; } [Required] public double SensorHeight { get; set; } } public record SaveResult { public bool Success { get; set; } public string? Exception { get; set; } } public record DownloadTileRequest { [Required] public double Latitude { get; set; } [Required] public double Longitude { get; set; } [Required] public int ZoomLevel { get; set; } = 20; } public record DownloadTileResponse { public Guid Id { get; set; } public int ZoomLevel { get; set; } public double Latitude { get; set; } public double Longitude { get; set; } public double TileSizeMeters { get; set; } public int TileSizePixels { get; set; } public string ImageType { get; set; } = string.Empty; public string? MapsVersion { get; set; } public string FilePath { get; set; } = string.Empty; public DateTime CreatedAt { get; set; } public DateTime UpdatedAt { get; set; } } public class ParameterDescriptionFilter : IOperationFilter { public void Apply(OpenApiOperation operation, OperationFilterContext context) { if (operation.Parameters == null) return; var parameterDescriptions = new Dictionary { ["lat"] = "Latitude coordinate where image was captured", ["lon"] = "Longitude coordinate where image was captured", ["mgrs"] = "MGRS coordinate string", ["squareSideMeters"] = "Square side size in meters" }; foreach (var parameter in operation.Parameters) { if (parameterDescriptions.TryGetValue(parameter.Name, out var description)) { parameter.Description = description; } } } }