Format ./web/backend/Features/Features.Breweries

This commit is contained in:
Aaron Po
2026-06-20 15:09:41 -04:00
parent 4de455f34d
commit 0f77ae43b5
18 changed files with 101 additions and 73 deletions

View File

@@ -22,4 +22,4 @@ public record CreateBreweryCommand(
string BreweryName,
string Description,
CreateBreweryLocation Location
) : IRequest<BreweryDto>;
) : IRequest<BreweryDto>;

View File

@@ -18,7 +18,10 @@ public class CreateBreweryHandler(IBreweryRepository repository)
/// <param name="request">The details of the brewery post to create.</param>
/// <param name="cancellationToken">A token to observe for cancellation requests.</param>
/// <returns>The newly created brewery post.</returns>
public async Task<BreweryDto> Handle(CreateBreweryCommand request, CancellationToken cancellationToken)
public async Task<BreweryDto> Handle(
CreateBreweryCommand request,
CancellationToken cancellationToken
)
{
BreweryPost entity = new()
{
@@ -34,11 +37,11 @@ public class CreateBreweryHandler(IBreweryRepository repository)
AddressLine1 = request.Location.AddressLine1,
AddressLine2 = request.Location.AddressLine2,
PostalCode = request.Location.PostalCode,
Coordinates = request.Location.Coordinates
}
Coordinates = request.Location.Coordinates,
},
};
await repository.CreateAsync(entity);
return entity.ToDto();
}
}
}

View File

@@ -15,9 +15,7 @@ public class CreateBreweryValidator : AbstractValidator<CreateBreweryCommand>
/// </summary>
public CreateBreweryValidator()
{
RuleFor(x => x.PostedById)
.NotEmpty()
.WithMessage("PostedById is required.");
RuleFor(x => x.PostedById).NotEmpty().WithMessage("PostedById is required.");
RuleFor(x => x.BreweryName)
.NotEmpty()
@@ -31,9 +29,7 @@ public class CreateBreweryValidator : AbstractValidator<CreateBreweryCommand>
.MaximumLength(512)
.WithMessage("Description cannot exceed 512 characters.");
RuleFor(x => x.Location)
.NotNull()
.WithMessage("Location is required.");
RuleFor(x => x.Location).NotNull().WithMessage("Location is required.");
RuleFor(x => x.Location.CityId)
.NotEmpty()
@@ -56,4 +52,4 @@ public class CreateBreweryValidator : AbstractValidator<CreateBreweryCommand>
.When(x => x.Location is not null)
.WithMessage("Postal code cannot exceed 20 characters.");
}
}
}

View File

@@ -5,4 +5,4 @@ namespace Features.Breweries.Commands.DeleteBrewery;
/// <summary>
/// Deletes a brewery post by its unique identifier.
/// </summary>
public record DeleteBreweryCommand(Guid BreweryPostId) : IRequest;
public record DeleteBreweryCommand(Guid BreweryPostId) : IRequest;

View File

@@ -14,4 +14,4 @@ public class DeleteBreweryHandler(IBreweryRepository repository)
{
return repository.DeleteAsync(request.BreweryPostId);
}
}
}

View File

@@ -25,4 +25,4 @@ public record UpdateBreweryCommand(
string BreweryName,
string Description,
UpdateBreweryLocation? Location
) : IRequest<BreweryDto>;
) : IRequest<BreweryDto>;

View File

@@ -19,7 +19,10 @@ public class UpdateBreweryHandler(IBreweryRepository repository)
/// <param name="request">The updated details of the brewery post.</param>
/// <param name="cancellationToken">A token to observe for cancellation requests.</param>
/// <returns>The updated brewery post.</returns>
public async Task<BreweryDto> Handle(UpdateBreweryCommand request, CancellationToken cancellationToken)
public async Task<BreweryDto> Handle(
UpdateBreweryCommand request,
CancellationToken cancellationToken
)
{
BreweryPost entity = new()
{
@@ -38,11 +41,11 @@ public class UpdateBreweryHandler(IBreweryRepository repository)
AddressLine1 = request.Location.AddressLine1,
AddressLine2 = request.Location.AddressLine2,
PostalCode = request.Location.PostalCode,
Coordinates = request.Location.Coordinates
}
Coordinates = request.Location.Coordinates,
},
};
await repository.UpdateAsync(entity);
return entity.ToDto();
}
}
}

View File

@@ -41,11 +41,13 @@ public class BreweryController(IMediator mediator) : ControllerBase
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 = brewery
});
return Ok(
new ResponseBody<BreweryDto>
{
Message = "Brewery retrieved successfully.",
Payload = brewery,
}
);
}
/// <summary>
@@ -59,14 +61,19 @@ public class BreweryController(IMediator mediator) : ControllerBase
[HttpGet]
public async Task<ActionResult<ResponseBody<IEnumerable<BreweryDto>>>> GetAll(
[FromQuery] int? limit,
[FromQuery] int? offset)
[FromQuery] int? offset
)
{
IEnumerable<BreweryDto> breweries = await mediator.Send(new GetAllBreweriesQuery(limit, offset));
return Ok(new ResponseBody<IEnumerable<BreweryDto>>
{
Message = "Breweries retrieved successfully.",
Payload = breweries
});
IEnumerable<BreweryDto> breweries = await mediator.Send(
new GetAllBreweriesQuery(limit, offset)
);
return Ok(
new ResponseBody<IEnumerable<BreweryDto>>
{
Message = "Breweries retrieved successfully.",
Payload = breweries,
}
);
}
/// <summary>
@@ -78,14 +85,19 @@ public class BreweryController(IMediator mediator) : ControllerBase
/// <see cref="BreweryDto" />.
/// </returns>
[HttpPost]
public async Task<ActionResult<ResponseBody<BreweryDto>>> Create([FromBody] CreateBreweryCommand command)
public async Task<ActionResult<ResponseBody<BreweryDto>>> Create(
[FromBody] CreateBreweryCommand command
)
{
BreweryDto brewery = await mediator.Send(command);
return Created($"/api/brewery/{brewery.BreweryPostId}", new ResponseBody<BreweryDto>
{
Message = "Brewery created successfully.",
Payload = brewery
});
return Created(
$"/api/brewery/{brewery.BreweryPostId}",
new ResponseBody<BreweryDto>
{
Message = "Brewery created successfully.",
Payload = brewery,
}
);
}
/// <summary>
@@ -105,17 +117,22 @@ public class BreweryController(IMediator mediator) : ControllerBase
/// when the route ID does not match the payload ID.
/// </returns>
[HttpPut("{id:guid}")]
public async Task<ActionResult<ResponseBody<BreweryDto>>> Update(Guid id, [FromBody] UpdateBreweryCommand command)
public async Task<ActionResult<ResponseBody<BreweryDto>>> Update(
Guid id,
[FromBody] UpdateBreweryCommand command
)
{
if (command.BreweryPostId != id)
return BadRequest(new ResponseBody { Message = "Route ID does not match payload ID." });
BreweryDto brewery = await mediator.Send(command);
return Ok(new ResponseBody<BreweryDto>
{
Message = "Brewery updated successfully.",
Payload = brewery
});
return Ok(
new ResponseBody<BreweryDto>
{
Message = "Brewery updated successfully.",
Payload = brewery,
}
);
}
/// <summary>
@@ -129,4 +146,4 @@ public class BreweryController(IMediator mediator) : ControllerBase
await mediator.Send(new DeleteBreweryCommand(id));
return Ok(new ResponseBody { Message = "Brewery deleted successfully." });
}
}
}

View File

@@ -13,4 +13,4 @@ public static class FeaturesBreweriesServiceCollectionExtensions
services.AddScoped<IBreweryRepository, BreweryRepository>();
return services;
}
}
}

View File

@@ -86,4 +86,4 @@ public class BreweryDto
/// The location details of the brewery, or <c>null</c> if no location is associated with it.
/// </summary>
public BreweryLocationDto? Location { get; set; }
}
}

View File

@@ -34,8 +34,8 @@ public static class BreweryDtoMapper
AddressLine1 = brewery.Location.AddressLine1,
AddressLine2 = brewery.Location.AddressLine2,
PostalCode = brewery.Location.PostalCode,
Coordinates = brewery.Location.Coordinates
}
Coordinates = brewery.Location.Coordinates,
},
};
}
}
}

View File

@@ -7,13 +7,13 @@
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj"/>
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Sql\Infrastructure.Sql.csproj"/>
<ProjectReference Include="..\..\Shared\Shared.Contracts\Shared.Contracts.csproj"/>
<ProjectReference Include="..\..\Shared\Shared.Application\Shared.Application.csproj"/>
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj" />
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Sql\Infrastructure.Sql.csproj" />
<ProjectReference Include="..\..\Shared\Shared.Contracts\Shared.Contracts.csproj" />
<ProjectReference Include="..\..\Shared\Shared.Application\Shared.Application.csproj" />
</ItemGroup>
</Project>

View File

@@ -12,9 +12,15 @@ namespace Features.Breweries.Queries.GetAllBreweries;
public class GetAllBreweriesHandler(IBreweryRepository repository)
: IRequestHandler<GetAllBreweriesQuery, IEnumerable<BreweryDto>>
{
public async Task<IEnumerable<BreweryDto>> Handle(GetAllBreweriesQuery request, CancellationToken cancellationToken)
public async Task<IEnumerable<BreweryDto>> Handle(
GetAllBreweriesQuery request,
CancellationToken cancellationToken
)
{
IEnumerable<BreweryPost> breweries = await repository.GetAllAsync(request.Limit, request.Offset);
IEnumerable<BreweryPost> breweries = await repository.GetAllAsync(
request.Limit,
request.Offset
);
return breweries.Select(b => b.ToDto());
}
}
}

View File

@@ -6,4 +6,4 @@ namespace Features.Breweries.Queries.GetAllBreweries;
/// <summary>
/// Retrieves a paginated list of brewery posts.
/// </summary>
public record GetAllBreweriesQuery(int? Limit, int? Offset) : IRequest<IEnumerable<BreweryDto>>;
public record GetAllBreweriesQuery(int? Limit, int? Offset) : IRequest<IEnumerable<BreweryDto>>;

View File

@@ -12,9 +12,12 @@ namespace Features.Breweries.Queries.GetBreweryById;
public class GetBreweryByIdHandler(IBreweryRepository repository)
: IRequestHandler<GetBreweryByIdQuery, BreweryDto?>
{
public async Task<BreweryDto?> Handle(GetBreweryByIdQuery request, CancellationToken cancellationToken)
public async Task<BreweryDto?> Handle(
GetBreweryByIdQuery request,
CancellationToken cancellationToken
)
{
BreweryPost? brewery = await repository.GetByIdAsync(request.BreweryPostId);
return brewery?.ToDto();
}
}
}

View File

@@ -6,4 +6,4 @@ namespace Features.Breweries.Queries.GetBreweryById;
/// <summary>
/// Retrieves a single brewery post by its unique identifier.
/// </summary>
public record GetBreweryByIdQuery(Guid BreweryPostId) : IRequest<BreweryDto?>;
public record GetBreweryByIdQuery(Guid BreweryPostId) : IRequest<BreweryDto?>;

View File

@@ -11,7 +11,8 @@ namespace Features.Breweries.Repository;
/// </summary>
/// <param name="connectionFactory">The factory used to create database connections.</param>
public class BreweryRepository(ISqlConnectionFactory connectionFactory)
: Repository<BreweryPost>(connectionFactory), IBreweryRepository
: Repository<BreweryPost>(connectionFactory),
IBreweryRepository
{
/// <summary>
/// Retrieves a brewery post by ID using the <c>USP_GetBreweryById</c> stored procedure.
@@ -29,7 +30,8 @@ public class BreweryRepository(ISqlConnectionFactory connectionFactory)
AddParameter(command, "@BreweryPostID", id);
await using DbDataReader reader = await command.ExecuteReaderAsync();
if (await reader.ReadAsync()) return MapToEntity(reader);
if (await reader.ReadAsync())
return MapToEntity(reader);
return null;
}
@@ -58,7 +60,8 @@ public class BreweryRepository(ISqlConnectionFactory connectionFactory)
await using DbDataReader reader = await command.ExecuteReaderAsync();
List<BreweryPost> breweries = new();
while (await reader.ReadAsync()) breweries.Add(MapToEntity(reader));
while (await reader.ReadAsync())
breweries.Add(MapToEntity(reader));
return breweries;
}
@@ -121,7 +124,8 @@ public class BreweryRepository(ISqlConnectionFactory connectionFactory)
command.CommandText = "USP_CreateBrewery";
command.CommandType = CommandType.StoredProcedure;
if (brewery.Location is null) throw new ArgumentException("Location must be provided when creating a brewery.");
if (brewery.Location is null)
throw new ArgumentException("Location must be provided when creating a brewery.");
AddParameter(command, "@BreweryName", brewery.BreweryName);
AddParameter(command, "@Description", brewery.Description);
@@ -195,7 +199,7 @@ public class BreweryRepository(ISqlConnectionFactory connectionFactory)
PostalCode = reader.GetString(reader.GetOrdinal("PostalCode")),
Coordinates = reader.IsDBNull(reader.GetOrdinal("Coordinates"))
? null
: reader.GetFieldValue<byte[]>(reader.GetOrdinal("Coordinates"))
: reader.GetFieldValue<byte[]>(reader.GetOrdinal("Coordinates")),
};
brewery.Location = location;
}
@@ -215,15 +219,11 @@ public class BreweryRepository(ISqlConnectionFactory connectionFactory)
/// <param name="command">The command to add the parameter to.</param>
/// <param name="name">The parameter name (including any prefix, e.g. "@BreweryName").</param>
/// <param name="value">The parameter value, or <c>null</c> to bind <see cref="DBNull.Value" />.</param>
private static void AddParameter(
DbCommand command,
string name,
object? value
)
private static void AddParameter(DbCommand command, string name, object? value)
{
DbParameter p = command.CreateParameter();
p.ParameterName = name;
p.Value = value ?? DBNull.Value;
command.Parameters.Add(p);
}
}
}

View File

@@ -40,4 +40,4 @@ public interface IBreweryRepository
/// <param name="brewery">The brewery post to create. Must have a non-null <c>Location</c>.</param>
/// <exception cref="ArgumentException">Thrown when <paramref name="brewery" /> has no <c>Location</c>.</exception>
Task CreateAsync(BreweryPost brewery);
}
}