DTO updates

This commit is contained in:
Aaron Po
2026-03-30 00:06:20 -04:00
parent 70ad06eeda
commit ebd162ec1b
12 changed files with 332 additions and 153 deletions

View File

@@ -31,6 +31,7 @@
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Repository\Infrastructure.Repository.csproj" /> <ProjectReference Include="..\..\Infrastructure\Infrastructure.Repository\Infrastructure.Repository.csproj" />
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Jwt\Infrastructure.Jwt.csproj" /> <ProjectReference Include="..\..\Infrastructure\Infrastructure.Jwt\Infrastructure.Jwt.csproj" />
<ProjectReference Include="..\..\Service\Service.Auth\Service.Auth.csproj" /> <ProjectReference Include="..\..\Service\Service.Auth\Service.Auth.csproj" />
<ProjectReference Include="..\..\Service\Service.Breweries\Service.Breweries.csproj" />
<ProjectReference Include="..\..\Service\Service.UserManagement\Service.UserManagement.csproj" /> <ProjectReference Include="..\..\Service\Service.UserManagement\Service.UserManagement.csproj" />
</ItemGroup> </ItemGroup>

View File

@@ -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();
}

View File

@@ -0,0 +1,50 @@
using FluentValidation;
namespace API.Core.Contracts.Breweries;
public class BreweryCreateDtoValidator : AbstractValidator<BreweryCreateDto>
{
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.");
}
}

View File

@@ -1,7 +1,33 @@
using System;
namespace API.Core.Contracts.Breweries; 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 class BreweryDto
{ {
public Guid BreweryPostId { get; set; } public Guid BreweryPostId { get; set; }

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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<ActionResult<ResponseBody<BreweryDto>>> 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<BreweryDto>
{
Message = "Brewery retrieved successfully.",
Payload = MapToDto(brewery),
});
}
[AllowAnonymous]
[HttpGet]
public async Task<ActionResult<ResponseBody<IEnumerable<BreweryDto>>>> GetAll(
[FromQuery] int? limit,
[FromQuery] int? offset)
{
var breweries = await breweryService.GetAllAsync(limit, offset);
return Ok(new ResponseBody<IEnumerable<BreweryDto>>
{
Message = "Breweries retrieved successfully.",
Payload = breweries.Select(MapToDto),
});
}
[HttpPost]
public async Task<ActionResult<ResponseBody<BreweryDto>>> 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<BreweryDto>
{
Message = "Brewery created successfully.",
Payload = MapToDto(result.Brewery),
});
}
[HttpPut("{id:guid}")]
public async Task<ActionResult<ResponseBody<BreweryDto>>> 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<BreweryDto>
{
Message = "Brewery updated successfully.",
Payload = MapToDto(result.Brewery),
});
}
[HttpDelete("{id:guid}")]
public async Task<ActionResult<ResponseBody>> 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,
},
};
}

View File

@@ -26,6 +26,7 @@
<Project Path="Service/Service.Auth.Tests/Service.Auth.Tests.csproj" /> <Project Path="Service/Service.Auth.Tests/Service.Auth.Tests.csproj" />
<Project Path="Service/Service.Emails/Service.Emails.csproj" /> <Project Path="Service/Service.Emails/Service.Emails.csproj" />
<Project Path="Service/Service.UserManagement/Service.UserManagement.csproj" /> <Project Path="Service/Service.UserManagement/Service.UserManagement.csproj" />
<Project Path="Service\Service.Auth\Service.Auth.csproj" /> <Project Path="Service/Service.Auth/Service.Auth.csproj" />
<Project Path="Service/Service.Breweries/Service.Breweries.csproj" />
</Folder> </Folder>
</Solution> </Solution>

View File

@@ -1,72 +1,65 @@
using Domain.Entities; using Domain.Entities;
using Infrastructure.Repository.Breweries; using Infrastructure.Repository.Breweries;
using API.Core.Contracts.Breweries;
namespace Service.Breweries; namespace Service.Breweries;
public class BreweryService(IBreweryRepository repository) : IBreweryService public class BreweryService(IBreweryRepository repository) : IBreweryService
{ {
private readonly IBreweryRepository _repository = repository; public Task<BreweryPost?> GetByIdAsync(Guid id) =>
repository.GetByIdAsync(id);
public Task<BreweryPost?> GetByIdAsync(Guid id) => _repository.GetByIdAsync(id); public Task<IEnumerable<BreweryPost>> GetAllAsync(int? limit = null, int? offset = null) =>
repository.GetAllAsync(limit, offset);
public Task<IEnumerable<BreweryPost>> GetAllAsync(int? limit = null, int? offset = null) => _repository.GetAllAsync(limit, offset); public async Task<BreweryServiceReturn> CreateAsync(BreweryCreateRequest request)
public async Task<BreweryServiceReturn> CreateAsync(BreweryCreateDto brewery)
{ {
if (brewery.Location is null)
return new BreweryServiceReturn("Location must be provided");
var entity = new BreweryPost var entity = new BreweryPost
{ {
BreweryPostId = Guid.NewGuid(), BreweryPostId = Guid.NewGuid(),
PostedById = brewery.PostedById, PostedById = request.PostedById,
BreweryName = brewery.BreweryName, BreweryName = request.BreweryName,
Description = brewery.Description, Description = request.Description,
CreatedAt = DateTime.UtcNow, CreatedAt = DateTime.UtcNow,
Location = new BreweryPostLocation Location = new BreweryPostLocation
{ {
BreweryPostLocationId = Guid.NewGuid(), BreweryPostLocationId = Guid.NewGuid(),
CityId = brewery.Location.CityId, CityId = request.Location.CityId,
AddressLine1 = brewery.Location.AddressLine1, AddressLine1 = request.Location.AddressLine1,
AddressLine2 = brewery.Location.AddressLine2, AddressLine2 = request.Location.AddressLine2,
PostalCode = brewery.Location.PostalCode, PostalCode = request.Location.PostalCode,
Coordinates = brewery.Location.Coordinates Coordinates = request.Location.Coordinates,
} },
}; };
await _repository.CreateAsync(entity); await repository.CreateAsync(entity);
return new BreweryServiceReturn(entity); return new BreweryServiceReturn(entity);
} }
public async Task<BreweryServiceReturn> UpdateAsync(BreweryDto brewery) public async Task<BreweryServiceReturn> UpdateAsync(BreweryUpdateRequest request)
{ {
if (brewery is null) return new BreweryServiceReturn("Brewery payload is null");
var entity = new BreweryPost var entity = new BreweryPost
{ {
BreweryPostId = brewery.BreweryPostId, BreweryPostId = request.BreweryPostId,
PostedById = brewery.PostedById, PostedById = request.PostedById,
BreweryName = brewery.BreweryName, BreweryName = request.BreweryName,
Description = brewery.Description, Description = request.Description,
CreatedAt = brewery.CreatedAt, UpdatedAt = DateTime.UtcNow,
UpdatedAt = brewery.UpdatedAt, Location = request.Location is null ? null : new BreweryPostLocation
Timer = brewery.Timer,
Location = brewery.Location is null ? null : new BreweryPostLocation
{ {
BreweryPostLocationId = brewery.Location.BreweryPostLocationId, BreweryPostLocationId = request.Location.BreweryPostLocationId,
BreweryPostId = brewery.BreweryPostId, BreweryPostId = request.BreweryPostId,
CityId = brewery.Location.CityId, CityId = request.Location.CityId,
AddressLine1 = brewery.Location.AddressLine1, AddressLine1 = request.Location.AddressLine1,
AddressLine2 = brewery.Location.AddressLine2, AddressLine2 = request.Location.AddressLine2,
PostalCode = brewery.Location.PostalCode, PostalCode = request.Location.PostalCode,
Coordinates = brewery.Location.Coordinates Coordinates = request.Location.Coordinates,
} },
}; };
await _repository.UpdateAsync(entity); await repository.UpdateAsync(entity);
return new BreweryServiceReturn(entity); return new BreweryServiceReturn(entity);
} }
public Task DeleteAsync(Guid id) => _repository.DeleteAsync(id); public Task DeleteAsync(Guid id) =>
repository.DeleteAsync(id);
} }

View File

@@ -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<IBreweryService, BreweryService>();
return services;
}
}

View File

@@ -1,20 +1,42 @@
using API.Core.Contracts.Breweries;
using Domain.Entities; using Domain.Entities;
namespace Service.Breweries; namespace Service.Breweries;
public interface IBreweryService public record BreweryCreateRequest(
{ Guid PostedById,
Task<BreweryPost?> GetByIdAsync(Guid id); string BreweryName,
Task<IEnumerable<BreweryPost>> GetAllAsync(int? limit = null, int? offset = null); string Description,
Task<BreweryServiceReturn> CreateAsync(BreweryCreateDto brewery); BreweryLocationCreateRequest Location
Task<BreweryServiceReturn> UpdateAsync(BreweryDto brewery); );
Task DeleteAsync(Guid id);
} 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 record BreweryServiceReturn
{ {
public bool Success { get; init; } = false; public bool Success { get; init; }
public BreweryPost Brewery { get; init; } public BreweryPost Brewery { get; init; }
public string Message { get; init; } = string.Empty; public string Message { get; init; } = string.Empty;
@@ -31,3 +53,12 @@ public record BreweryServiceReturn
Message = message; Message = message;
} }
} }
public interface IBreweryService
{
Task<BreweryPost?> GetByIdAsync(Guid id);
Task<IEnumerable<BreweryPost>> GetAllAsync(int? limit = null, int? offset = null);
Task<BreweryServiceReturn> CreateAsync(BreweryCreateRequest request);
Task<BreweryServiceReturn> UpdateAsync(BreweryUpdateRequest request);
Task DeleteAsync(Guid id);
}

View File

@@ -7,9 +7,6 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj" /> <ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj" />
<ProjectReference <ProjectReference Include="..\..\Infrastructure\Infrastructure.Repository\Infrastructure.Repository.csproj" />
Include="..\..\Infrastructure\Infrastructure.Repository\Infrastructure.Repository.csproj" />
<ProjectReference Include="..\..\API\API.Core\API.Core.csproj" />
<ProjectReference Include="..\Service.Auth\Service.Auth.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>