From ebd162ec1b504076de2f1771a184221b00536a99 Mon Sep 17 00:00:00 2001 From: Aaron Po Date: Mon, 30 Mar 2026 00:06:20 -0400 Subject: [PATCH] DTO updates --- src/Core/API/API.Core/API.Core.csproj | 1 + .../Contracts/Breweries/BreweryCreateDto.cs | 11 -- .../BreweryCreateRequestValidator.cs | 50 +++++++ .../Contracts/Breweries/BreweryDto.cs | 46 +++++-- .../Breweries/BreweryLocationCreateDto.cs | 12 -- .../Contracts/Breweries/BreweryLocationDto.cs | 14 -- .../API.Core/Controllers/BreweryController.cs | 129 ++++++++++++++++++ src/Core/Core.slnx | 3 +- .../Service.Breweries/BreweryService.cs | 109 +++++++-------- .../BreweryServiceCollectionExtensions.cs | 12 -- .../Service.Breweries/IBreweryService.cs | 77 +++++++---- .../Service.Breweries.csproj | 21 ++- 12 files changed, 332 insertions(+), 153 deletions(-) delete mode 100644 src/Core/API/API.Core/Contracts/Breweries/BreweryCreateDto.cs create mode 100644 src/Core/API/API.Core/Contracts/Breweries/BreweryCreateRequestValidator.cs delete mode 100644 src/Core/API/API.Core/Contracts/Breweries/BreweryLocationCreateDto.cs delete mode 100644 src/Core/API/API.Core/Contracts/Breweries/BreweryLocationDto.cs create mode 100644 src/Core/API/API.Core/Controllers/BreweryController.cs delete mode 100644 src/Core/Service/Service.Breweries/DependencyInjection/BreweryServiceCollectionExtensions.cs diff --git a/src/Core/API/API.Core/API.Core.csproj b/src/Core/API/API.Core/API.Core.csproj index 61c6ba5..aab193e 100644 --- a/src/Core/API/API.Core/API.Core.csproj +++ b/src/Core/API/API.Core/API.Core.csproj @@ -31,6 +31,7 @@ + diff --git a/src/Core/API/API.Core/Contracts/Breweries/BreweryCreateDto.cs b/src/Core/API/API.Core/Contracts/Breweries/BreweryCreateDto.cs deleted file mode 100644 index 342d125..0000000 --- a/src/Core/API/API.Core/Contracts/Breweries/BreweryCreateDto.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace API.Core.Contracts.Breweries; - -public class BreweryCreateDto -{ - public Guid PostedById { get; set; } - public string BreweryName { get; set; } = string.Empty; - public string Description { get; set; } = string.Empty; - public BreweryLocationCreateDto Location { get; set; } = new BreweryLocationCreateDto(); -} diff --git a/src/Core/API/API.Core/Contracts/Breweries/BreweryCreateRequestValidator.cs b/src/Core/API/API.Core/Contracts/Breweries/BreweryCreateRequestValidator.cs new file mode 100644 index 0000000..b181a74 --- /dev/null +++ b/src/Core/API/API.Core/Contracts/Breweries/BreweryCreateRequestValidator.cs @@ -0,0 +1,50 @@ +using FluentValidation; + +namespace API.Core.Contracts.Breweries; + +public class BreweryCreateDtoValidator : AbstractValidator +{ + public BreweryCreateDtoValidator() + { + RuleFor(x => x.PostedById) + .NotEmpty() + .WithMessage("PostedById is required."); + + RuleFor(x => x.BreweryName) + .NotEmpty() + .WithMessage("Brewery name is required.") + .MaximumLength(256) + .WithMessage("Brewery name cannot exceed 256 characters."); + + RuleFor(x => x.Description) + .NotEmpty() + .WithMessage("Description is required.") + .MaximumLength(512) + .WithMessage("Description cannot exceed 512 characters."); + + RuleFor(x => x.Location) + .NotNull() + .WithMessage("Location is required."); + + RuleFor(x => x.Location.CityId) + .NotEmpty() + .When(x => x.Location is not null) + .WithMessage("CityId is required."); + + RuleFor(x => x.Location.AddressLine1) + .NotEmpty() + .When(x => x.Location is not null) + .WithMessage("Address line 1 is required.") + .MaximumLength(256) + .When(x => x.Location is not null) + .WithMessage("Address line 1 cannot exceed 256 characters."); + + RuleFor(x => x.Location.PostalCode) + .NotEmpty() + .When(x => x.Location is not null) + .WithMessage("Postal code is required.") + .MaximumLength(20) + .When(x => x.Location is not null) + .WithMessage("Postal code cannot exceed 20 characters."); + } +} diff --git a/src/Core/API/API.Core/Contracts/Breweries/BreweryDto.cs b/src/Core/API/API.Core/Contracts/Breweries/BreweryDto.cs index 3bd7c06..c6ff6bf 100644 --- a/src/Core/API/API.Core/Contracts/Breweries/BreweryDto.cs +++ b/src/Core/API/API.Core/Contracts/Breweries/BreweryDto.cs @@ -1,15 +1,41 @@ -using System; - namespace API.Core.Contracts.Breweries; +public class BreweryLocationCreateDto +{ + public Guid CityId { get; set; } + public string AddressLine1 { get; set; } = string.Empty; + public string? AddressLine2 { get; set; } + public string PostalCode { get; set; } = string.Empty; + public byte[]? Coordinates { get; set; } +} + +public class BreweryLocationDto +{ + public Guid BreweryPostLocationId { get; set; } + public Guid BreweryPostId { get; set; } + public Guid CityId { get; set; } + public string AddressLine1 { get; set; } = string.Empty; + public string? AddressLine2 { get; set; } + public string PostalCode { get; set; } = string.Empty; + public byte[]? Coordinates { get; set; } +} + +public class BreweryCreateDto +{ + public Guid PostedById { get; set; } + public string BreweryName { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public BreweryLocationCreateDto Location { get; set; } = null!; +} + public class BreweryDto { - public Guid BreweryPostId { get; set; } - public Guid PostedById { get; set; } - public string BreweryName { get; set; } = string.Empty; - public string Description { get; set; } = string.Empty; - public DateTime CreatedAt { get; set; } - public DateTime? UpdatedAt { get; set; } - public byte[]? Timer { get; set; } - public BreweryLocationDto? Location { get; set; } + public Guid BreweryPostId { get; set; } + public Guid PostedById { get; set; } + public string BreweryName { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + public byte[]? Timer { get; set; } + public BreweryLocationDto? Location { get; set; } } diff --git a/src/Core/API/API.Core/Contracts/Breweries/BreweryLocationCreateDto.cs b/src/Core/API/API.Core/Contracts/Breweries/BreweryLocationCreateDto.cs deleted file mode 100644 index 8844981..0000000 --- a/src/Core/API/API.Core/Contracts/Breweries/BreweryLocationCreateDto.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace API.Core.Contracts.Breweries; - -public class BreweryLocationCreateDto -{ - public Guid CityId { get; set; } - public string AddressLine1 { get; set; } = string.Empty; - public string? AddressLine2 { get; set; } - public string PostalCode { get; set; } = string.Empty; - public byte[]? Coordinates { get; set; } -} diff --git a/src/Core/API/API.Core/Contracts/Breweries/BreweryLocationDto.cs b/src/Core/API/API.Core/Contracts/Breweries/BreweryLocationDto.cs deleted file mode 100644 index 0c6173c..0000000 --- a/src/Core/API/API.Core/Contracts/Breweries/BreweryLocationDto.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace API.Core.Contracts.Breweries; - -public class BreweryLocationDto -{ - public Guid BreweryPostLocationId { get; set; } - public Guid BreweryPostId { get; set; } - public Guid CityId { get; set; } - public string AddressLine1 { get; set; } = string.Empty; - public string? AddressLine2 { get; set; } - public string PostalCode { get; set; } = string.Empty; - public byte[]? Coordinates { get; set; } -} diff --git a/src/Core/API/API.Core/Controllers/BreweryController.cs b/src/Core/API/API.Core/Controllers/BreweryController.cs new file mode 100644 index 0000000..d0fb8d4 --- /dev/null +++ b/src/Core/API/API.Core/Controllers/BreweryController.cs @@ -0,0 +1,129 @@ +using API.Core.Contracts.Breweries; +using API.Core.Contracts.Common; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Service.Breweries; + +namespace API.Core.Controllers; + +[ApiController] +[Route("api/[controller]")] +[Authorize(AuthenticationSchemes = "JWT")] +public class BreweryController(IBreweryService breweryService) : ControllerBase +{ + [AllowAnonymous] + [HttpGet("{id:guid}")] + public async Task>> GetById(Guid id) + { + var brewery = await breweryService.GetByIdAsync(id); + if (brewery is null) + return NotFound(new ResponseBody { Message = $"Brewery with ID {id} not found." }); + + return Ok(new ResponseBody + { + Message = "Brewery retrieved successfully.", + Payload = MapToDto(brewery), + }); + } + + [AllowAnonymous] + [HttpGet] + public async Task>>> GetAll( + [FromQuery] int? limit, + [FromQuery] int? offset) + { + var breweries = await breweryService.GetAllAsync(limit, offset); + return Ok(new ResponseBody> + { + Message = "Breweries retrieved successfully.", + Payload = breweries.Select(MapToDto), + }); + } + + [HttpPost] + public async Task>> Create([FromBody] BreweryCreateDto dto) + { + var request = new BreweryCreateRequest( + dto.PostedById, + dto.BreweryName, + dto.Description, + new BreweryLocationCreateRequest( + dto.Location.CityId, + dto.Location.AddressLine1, + dto.Location.AddressLine2, + dto.Location.PostalCode, + dto.Location.Coordinates + ) + ); + + var result = await breweryService.CreateAsync(request); + if (!result.Success) + return BadRequest(new ResponseBody { Message = result.Message }); + + return Created($"/api/brewery/{result.Brewery.BreweryPostId}", new ResponseBody + { + Message = "Brewery created successfully.", + Payload = MapToDto(result.Brewery), + }); + } + + [HttpPut("{id:guid}")] + public async Task>> Update(Guid id, [FromBody] BreweryDto dto) + { + if (dto.BreweryPostId != id) + return BadRequest(new ResponseBody { Message = "Route ID does not match payload ID." }); + + var request = new BreweryUpdateRequest( + dto.BreweryPostId, + dto.PostedById, + dto.BreweryName, + dto.Description, + dto.Location is null ? null : new BreweryLocationUpdateRequest( + dto.Location.BreweryPostLocationId, + dto.Location.CityId, + dto.Location.AddressLine1, + dto.Location.AddressLine2, + dto.Location.PostalCode, + dto.Location.Coordinates + ) + ); + + var result = await breweryService.UpdateAsync(request); + if (!result.Success) + return BadRequest(new ResponseBody { Message = result.Message }); + + return Ok(new ResponseBody + { + Message = "Brewery updated successfully.", + Payload = MapToDto(result.Brewery), + }); + } + + [HttpDelete("{id:guid}")] + public async Task> Delete(Guid id) + { + await breweryService.DeleteAsync(id); + return Ok(new ResponseBody { Message = "Brewery deleted successfully." }); + } + + private static BreweryDto MapToDto(Domain.Entities.BreweryPost b) => new() + { + BreweryPostId = b.BreweryPostId, + PostedById = b.PostedById, + BreweryName = b.BreweryName, + Description = b.Description, + CreatedAt = b.CreatedAt, + UpdatedAt = b.UpdatedAt, + Timer = b.Timer, + Location = b.Location is null ? null : new BreweryLocationDto + { + BreweryPostLocationId = b.Location.BreweryPostLocationId, + BreweryPostId = b.Location.BreweryPostId, + CityId = b.Location.CityId, + AddressLine1 = b.Location.AddressLine1, + AddressLine2 = b.Location.AddressLine2, + PostalCode = b.Location.PostalCode, + Coordinates = b.Location.Coordinates, + }, + }; +} diff --git a/src/Core/Core.slnx b/src/Core/Core.slnx index b1ff5d5..6add115 100644 --- a/src/Core/Core.slnx +++ b/src/Core/Core.slnx @@ -26,6 +26,7 @@ - + + diff --git a/src/Core/Service/Service.Breweries/BreweryService.cs b/src/Core/Service/Service.Breweries/BreweryService.cs index 2338e3c..5966f6c 100644 --- a/src/Core/Service/Service.Breweries/BreweryService.cs +++ b/src/Core/Service/Service.Breweries/BreweryService.cs @@ -1,72 +1,65 @@ using Domain.Entities; using Infrastructure.Repository.Breweries; -using API.Core.Contracts.Breweries; namespace Service.Breweries; public class BreweryService(IBreweryRepository repository) : IBreweryService { - private readonly IBreweryRepository _repository = repository; + public Task GetByIdAsync(Guid id) => + repository.GetByIdAsync(id); - public Task GetByIdAsync(Guid id) => _repository.GetByIdAsync(id); + public Task> GetAllAsync(int? limit = null, int? offset = null) => + repository.GetAllAsync(limit, offset); - public Task> GetAllAsync(int? limit = null, int? offset = null) => _repository.GetAllAsync(limit, offset); + public async Task CreateAsync(BreweryCreateRequest request) + { + var entity = new BreweryPost + { + BreweryPostId = Guid.NewGuid(), + PostedById = request.PostedById, + BreweryName = request.BreweryName, + Description = request.Description, + CreatedAt = DateTime.UtcNow, + Location = new BreweryPostLocation + { + BreweryPostLocationId = Guid.NewGuid(), + CityId = request.Location.CityId, + AddressLine1 = request.Location.AddressLine1, + AddressLine2 = request.Location.AddressLine2, + PostalCode = request.Location.PostalCode, + Coordinates = request.Location.Coordinates, + }, + }; - public async Task CreateAsync(BreweryCreateDto brewery) - { - if (brewery.Location is null) - return new BreweryServiceReturn("Location must be provided"); + await repository.CreateAsync(entity); + return new BreweryServiceReturn(entity); + } - var entity = new BreweryPost - { - BreweryPostId = Guid.NewGuid(), - PostedById = brewery.PostedById, - BreweryName = brewery.BreweryName, - Description = brewery.Description, - CreatedAt = DateTime.UtcNow, - Location = new BreweryPostLocation - { - BreweryPostLocationId = Guid.NewGuid(), - CityId = brewery.Location.CityId, - AddressLine1 = brewery.Location.AddressLine1, - AddressLine2 = brewery.Location.AddressLine2, - PostalCode = brewery.Location.PostalCode, - Coordinates = brewery.Location.Coordinates - } - }; + public async Task UpdateAsync(BreweryUpdateRequest request) + { + var entity = new BreweryPost + { + BreweryPostId = request.BreweryPostId, + PostedById = request.PostedById, + BreweryName = request.BreweryName, + Description = request.Description, + UpdatedAt = DateTime.UtcNow, + Location = request.Location is null ? null : new BreweryPostLocation + { + BreweryPostLocationId = request.Location.BreweryPostLocationId, + BreweryPostId = request.BreweryPostId, + CityId = request.Location.CityId, + AddressLine1 = request.Location.AddressLine1, + AddressLine2 = request.Location.AddressLine2, + PostalCode = request.Location.PostalCode, + Coordinates = request.Location.Coordinates, + }, + }; - await _repository.CreateAsync(entity); - return new BreweryServiceReturn(entity); - } + await repository.UpdateAsync(entity); + return new BreweryServiceReturn(entity); + } - public async Task UpdateAsync(BreweryDto brewery) - { - if (brewery is null) return new BreweryServiceReturn("Brewery payload is null"); - - var entity = new BreweryPost - { - BreweryPostId = brewery.BreweryPostId, - PostedById = brewery.PostedById, - BreweryName = brewery.BreweryName, - Description = brewery.Description, - CreatedAt = brewery.CreatedAt, - UpdatedAt = brewery.UpdatedAt, - Timer = brewery.Timer, - Location = brewery.Location is null ? null : new BreweryPostLocation - { - BreweryPostLocationId = brewery.Location.BreweryPostLocationId, - BreweryPostId = brewery.BreweryPostId, - CityId = brewery.Location.CityId, - AddressLine1 = brewery.Location.AddressLine1, - AddressLine2 = brewery.Location.AddressLine2, - PostalCode = brewery.Location.PostalCode, - Coordinates = brewery.Location.Coordinates - } - }; - - await _repository.UpdateAsync(entity); - return new BreweryServiceReturn(entity); - } - - public Task DeleteAsync(Guid id) => _repository.DeleteAsync(id); + public Task DeleteAsync(Guid id) => + repository.DeleteAsync(id); } diff --git a/src/Core/Service/Service.Breweries/DependencyInjection/BreweryServiceCollectionExtensions.cs b/src/Core/Service/Service.Breweries/DependencyInjection/BreweryServiceCollectionExtensions.cs deleted file mode 100644 index 6884bbf..0000000 --- a/src/Core/Service/Service.Breweries/DependencyInjection/BreweryServiceCollectionExtensions.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace Service.Breweries.DependencyInjection; - -public static class BreweryServiceCollectionExtensions -{ - public static IServiceCollection AddBreweryServices(this IServiceCollection services) - { - services.AddScoped(); - return services; - } -} diff --git a/src/Core/Service/Service.Breweries/IBreweryService.cs b/src/Core/Service/Service.Breweries/IBreweryService.cs index b2ec445..5331634 100644 --- a/src/Core/Service/Service.Breweries/IBreweryService.cs +++ b/src/Core/Service/Service.Breweries/IBreweryService.cs @@ -1,33 +1,64 @@ -using API.Core.Contracts.Breweries; using Domain.Entities; namespace Service.Breweries; -public interface IBreweryService -{ - Task GetByIdAsync(Guid id); - Task> GetAllAsync(int? limit = null, int? offset = null); - Task CreateAsync(BreweryCreateDto brewery); - Task UpdateAsync(BreweryDto brewery); - Task DeleteAsync(Guid id); -} +public record BreweryCreateRequest( + Guid PostedById, + string BreweryName, + string Description, + BreweryLocationCreateRequest Location +); + +public record BreweryLocationCreateRequest( + Guid CityId, + string AddressLine1, + string? AddressLine2, + string PostalCode, + byte[]? Coordinates +); + +public record BreweryUpdateRequest( + Guid BreweryPostId, + Guid PostedById, + string BreweryName, + string Description, + BreweryLocationUpdateRequest? Location +); + +public record BreweryLocationUpdateRequest( + Guid BreweryPostLocationId, + Guid CityId, + string AddressLine1, + string? AddressLine2, + string PostalCode, + byte[]? Coordinates +); public record BreweryServiceReturn { - public bool Success { get; init; } = false; - public BreweryPost Brewery { get; init; } - public string Message { get; init; } = string.Empty; + public bool Success { get; init; } + public BreweryPost Brewery { get; init; } + public string Message { get; init; } = string.Empty; - public BreweryServiceReturn(BreweryPost brewery) - { - Success = true; - Brewery = brewery; - } + public BreweryServiceReturn(BreweryPost brewery) + { + Success = true; + Brewery = brewery; + } - public BreweryServiceReturn(string message) - { - Success = false; - Brewery = default!; - Message = message; - } + public BreweryServiceReturn(string message) + { + Success = false; + Brewery = default!; + Message = message; + } +} + +public interface IBreweryService +{ + Task GetByIdAsync(Guid id); + Task> GetAllAsync(int? limit = null, int? offset = null); + Task CreateAsync(BreweryCreateRequest request); + Task UpdateAsync(BreweryUpdateRequest request); + Task DeleteAsync(Guid id); } diff --git a/src/Core/Service/Service.Breweries/Service.Breweries.csproj b/src/Core/Service/Service.Breweries/Service.Breweries.csproj index 3ea9cbb..578b547 100644 --- a/src/Core/Service/Service.Breweries/Service.Breweries.csproj +++ b/src/Core/Service/Service.Breweries/Service.Breweries.csproj @@ -1,15 +1,12 @@ - - net10.0 - enable - enable - + + net10.0 + enable + enable + - - - - - - + + + +