using FluentValidation; using FluentValidation.Results; using Microsoft.EntityFrameworkCore; using Vegasco.Server.Api.Authentication; using Vegasco.Server.Api.Common; using Vegasco.Server.Api.Persistence; namespace Vegasco.Server.Api.Cars; public static class UpdateCar { public record Request(string Name); public record Response(Guid Id, string Name); public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder) { return builder .MapPut("cars/{id:guid}", Endpoint) .WithTags("Cars") .WithDescription("Updates a car by ID") .Produces() .ProducesValidationProblem() .Produces(404) .Produces(409); } public class Validator : AbstractValidator { public Validator() { RuleFor(x => x.Name) .NotEmpty() .MaximumLength(CarTableConfiguration.NameMaxLength); } } private static async Task Endpoint( Guid id, Request request, IEnumerable> validators, ApplicationDbContext dbContext, UserAccessor userAccessor, ILoggerFactory loggerFactory, CancellationToken cancellationToken) { ILogger logger = loggerFactory.CreateLogger(typeof(UpdateCar)); List failedValidations = await validators.ValidateAllAsync(request, cancellationToken); if (failedValidations.Count > 0) { logger.LogDebug( "Validation failed for request {@Request} with errors {@Errors}", request, failedValidations .Where(x => !x.IsValid) .SelectMany(x => x.Errors) .Select(x => x.ErrorMessage)); return TypedResults.BadRequest(new HttpValidationProblemDetails(failedValidations.ToCombinedDictionary())); } Car? car = await dbContext.Cars.FindAsync([new CarId(id)], cancellationToken: cancellationToken); if (car is null) { return TypedResults.NotFound(); } bool isDuplicate = await dbContext.Cars .AnyAsync(x => x.Name.ToUpper() == request.Name.ToUpper(), cancellationToken); if (isDuplicate) { logger.LogDebug("Car with name '{CarName}' (case insensitive) already exists", request.Name); return TypedResults.Conflict(); } car.Name = request.Name.Trim(); await dbContext.SaveChangesAsync(cancellationToken); logger.LogTrace("Updated car: {@Car}", car); Response response = new(car.Id.Value, car.Name); return TypedResults.Ok(response); } }