using Microsoft.EntityFrameworkCore; using OtaFleet.Api.Data; using OtaFleet.Api.Data.Entities; namespace OtaFleet.Api.Endpoints; public static class VehicleEndpoints { public static void MapVehicleEndpoints(this IEndpointRouteBuilder app) { var group = app.MapGroup("/api/vehicles"); group.MapPost("/register", async (OtaDbContext db, RegisterVehicleRequest request) => { var vehicle = await db.Vehicles.FindAsync(request.Vin); if (vehicle == null) { vehicle = new Vehicle { Vin = request.Vin, Status = "Online", CurrentVersion = request.CurrentVersion, LastHeartbeat = DateTime.UtcNow }; db.Vehicles.Add(vehicle); } else { vehicle.Status = "Online"; vehicle.CurrentVersion = request.CurrentVersion; vehicle.LastHeartbeat = DateTime.UtcNow; } await db.SaveChangesAsync(); return Results.Ok(vehicle); }); group.MapPost("/heartbeat", async (OtaDbContext db, HeartbeatRequest request) => { var vehicle = await db.Vehicles.FindAsync(request.Vin); if (vehicle == null) return Results.NotFound(); vehicle.LastHeartbeat = DateTime.UtcNow; vehicle.Status = request.Status; await db.SaveChangesAsync(); return Results.Ok(); }); group.MapGet("/updates", async (OtaDbContext db, string vin) => { var vehicle = await db.Vehicles.Include(v => v.Group).FirstOrDefaultAsync(v => v.Vin == vin); if (vehicle == null) return Results.NotFound(); // Find deployment for this vehicle or its group var deployment = await db.Deployments .Include(d => d.Update) .Where(d => (d.TargetVin == vin || (vehicle.GroupId.HasValue && d.TargetGroupId == vehicle.GroupId)) && d.Status != "Completed") // Simple logic, can be improved .OrderByDescending(d => d.CreatedAt) .FirstOrDefaultAsync(); if (deployment == null) return Results.NoContent(); // Check if vehicle already has this version if (deployment.Update.Version == vehicle.CurrentVersion) return Results.NoContent(); return Results.Ok(new VehicleUpdateResponse { UpdateId = deployment.Update.Id, Version = deployment.Update.Version, Description = deployment.Update.Description, DownloadUrl = $"/api/vehicles/updates/{deployment.Update.Id}/download" }); }); group.MapGet("/updates/{id}/download", async (OtaDbContext db, int id) => { var update = await db.FirmwareUpdates.FindAsync(id); if (update == null) return Results.NotFound(); if (!File.Exists(update.FilePath)) return Results.NotFound("File not found on server"); var fileName = Path.GetFileName(update.FilePath); var mimeType = "application/octet-stream"; return Results.File(update.FilePath, mimeType, fileName); }); } } public record RegisterVehicleRequest(string Vin, string CurrentVersion); public record HeartbeatRequest(string Vin, string Status); public record VehicleUpdateResponse { public int UpdateId { get; set; } public string Version { get; set; } = string.Empty; public string? Description { get; set; } public string DownloadUrl { get; set; } = string.Empty; }