mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-04-05 10:09:03 +00:00
Implement CRUD operations for Brewery, including service and repository layers
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
15
src/Core/API/API.Core/Contracts/Breweries/BreweryDto.cs
Normal file
15
src/Core/API/API.Core/Contracts/Breweries/BreweryDto.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace API.Core.Contracts.Breweries;
|
||||||
|
|
||||||
|
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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
@@ -4,24 +4,10 @@ using Infrastructure.Repository.Sql;
|
|||||||
|
|
||||||
namespace Infrastructure.Repository.Breweries;
|
namespace Infrastructure.Repository.Breweries;
|
||||||
|
|
||||||
public interface IBreweryRepository
|
public class BreweryRepository(ISqlConnectionFactory connectionFactory)
|
||||||
|
: Repository<BreweryPost>(connectionFactory), IBreweryRepository
|
||||||
{
|
{
|
||||||
Task<BreweryPost?> GetByIdAsync(Guid id);
|
private readonly ISqlConnectionFactory _connectionFactory = connectionFactory;
|
||||||
Task<IEnumerable<BreweryPost>> GetAllAsync(int? limit, int? offset);
|
|
||||||
Task UpdateAsync(BreweryPost brewery);
|
|
||||||
Task DeleteAsync(Guid id);
|
|
||||||
Task CreateAsync(BreweryPost brewery);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BreweryRepository : Repository<BreweryPost>, IBreweryRepository
|
|
||||||
{
|
|
||||||
private readonly ISqlConnectionFactory _connectionFactory;
|
|
||||||
|
|
||||||
public BreweryRepository(ISqlConnectionFactory connectionFactory)
|
|
||||||
: base(connectionFactory)
|
|
||||||
{
|
|
||||||
_connectionFactory = connectionFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<BreweryPost?> GetByIdAsync(Guid id)
|
public async Task<BreweryPost?> GetByIdAsync(Guid id)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using Domain.Entities;
|
||||||
|
|
||||||
|
namespace Infrastructure.Repository.Breweries;
|
||||||
|
|
||||||
|
public interface IBreweryRepository
|
||||||
|
{
|
||||||
|
Task<BreweryPost?> GetByIdAsync(Guid id);
|
||||||
|
Task<IEnumerable<BreweryPost>> GetAllAsync(int? limit, int? offset);
|
||||||
|
Task UpdateAsync(BreweryPost brewery);
|
||||||
|
Task DeleteAsync(Guid id);
|
||||||
|
Task CreateAsync(BreweryPost brewery);
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
using FluentAssertions;
|
||||||
|
using Xunit;
|
||||||
|
using Service.Breweries;
|
||||||
|
using API.Core.Contracts.Breweries;
|
||||||
|
using Domain.Entities;
|
||||||
|
|
||||||
|
namespace Service.Breweries.Tests;
|
||||||
|
|
||||||
|
public class BreweryServiceTests
|
||||||
|
{
|
||||||
|
private class FakeRepo : IBreweryRepository
|
||||||
|
{
|
||||||
|
public BreweryPost? Created;
|
||||||
|
|
||||||
|
public Task<BreweryPost?> GetByIdAsync(Guid id) => Task.FromResult<BreweryPost?>(null);
|
||||||
|
public Task<IEnumerable<BreweryPost>> GetAllAsync(int? limit, int? offset) => Task.FromResult<IEnumerable<BreweryPost>>(Array.Empty<BreweryPost>());
|
||||||
|
public Task UpdateAsync(BreweryPost brewery) { Created = brewery; return Task.CompletedTask; }
|
||||||
|
public Task DeleteAsync(Guid id) => Task.CompletedTask;
|
||||||
|
public Task CreateAsync(BreweryPost brewery) { Created = brewery; return Task.CompletedTask; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateAsync_ReturnsFailure_WhenLocationMissing()
|
||||||
|
{
|
||||||
|
var repo = new FakeRepo();
|
||||||
|
var svc = new BreweryService(repo);
|
||||||
|
|
||||||
|
var dto = new BreweryCreateDto
|
||||||
|
{
|
||||||
|
PostedById = Guid.NewGuid(),
|
||||||
|
BreweryName = "X",
|
||||||
|
Description = "Y",
|
||||||
|
Location = null!
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await svc.CreateAsync(dto);
|
||||||
|
result.Success.Should().BeFalse();
|
||||||
|
result.Message.Should().Contain("Location");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task CreateAsync_ReturnsSuccess_AndPersistsEntity()
|
||||||
|
{
|
||||||
|
var repo = new FakeRepo();
|
||||||
|
var svc = new BreweryService(repo);
|
||||||
|
|
||||||
|
var loc = new BreweryLocationCreateDto
|
||||||
|
{
|
||||||
|
CityId = Guid.NewGuid(),
|
||||||
|
AddressLine1 = "123 Main",
|
||||||
|
PostalCode = "12345"
|
||||||
|
};
|
||||||
|
|
||||||
|
var dto = new BreweryCreateDto
|
||||||
|
{
|
||||||
|
PostedById = Guid.NewGuid(),
|
||||||
|
BreweryName = "MyBrew",
|
||||||
|
Description = "Desc",
|
||||||
|
Location = loc
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await svc.CreateAsync(dto);
|
||||||
|
|
||||||
|
result.Success.Should().BeTrue();
|
||||||
|
repo.Created.Should().NotBeNull();
|
||||||
|
repo.Created!.BreweryName.Should().Be("MyBrew");
|
||||||
|
result.Brewery.BreweryName.Should().Be("MyBrew");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
<RootNamespace>Service.Breweries.Tests</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
|
||||||
|
<PackageReference Include="xunit" Version="2.9.2" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
|
||||||
|
<PackageReference Include="FluentAssertions" Version="6.9.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Service.Breweries\Service.Breweries.csproj" />
|
||||||
|
<ProjectReference Include="..\Service.Auth\Service.Auth.csproj" />
|
||||||
|
<ProjectReference
|
||||||
|
Include="..\..\Infrastructure\Infrastructure.Repository\Infrastructure.Repository.csproj" />
|
||||||
|
<ProjectReference Include="..\..\API\API.Core\API.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
72
src/Core/Service/Service.Breweries/BreweryService.cs
Normal file
72
src/Core/Service/Service.Breweries/BreweryService.cs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
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<BreweryPost?> GetByIdAsync(Guid id) => _repository.GetByIdAsync(id);
|
||||||
|
|
||||||
|
public Task<IEnumerable<BreweryPost>> GetAllAsync(int? limit = null, int? offset = null) => _repository.GetAllAsync(limit, offset);
|
||||||
|
|
||||||
|
public async Task<BreweryServiceReturn> CreateAsync(BreweryCreateDto brewery)
|
||||||
|
{
|
||||||
|
if (brewery.Location is null)
|
||||||
|
return new BreweryServiceReturn("Location must be provided");
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await _repository.CreateAsync(entity);
|
||||||
|
return new BreweryServiceReturn(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<BreweryServiceReturn> 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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/Core/Service/Service.Breweries/IBreweryService.cs
Normal file
33
src/Core/Service/Service.Breweries/IBreweryService.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using API.Core.Contracts.Breweries;
|
||||||
|
using Domain.Entities;
|
||||||
|
|
||||||
|
namespace Service.Breweries;
|
||||||
|
|
||||||
|
public interface IBreweryService
|
||||||
|
{
|
||||||
|
Task<BreweryPost?> GetByIdAsync(Guid id);
|
||||||
|
Task<IEnumerable<BreweryPost>> GetAllAsync(int? limit = null, int? offset = null);
|
||||||
|
Task<BreweryServiceReturn> CreateAsync(BreweryCreateDto brewery);
|
||||||
|
Task<BreweryServiceReturn> UpdateAsync(BreweryDto brewery);
|
||||||
|
Task DeleteAsync(Guid id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record BreweryServiceReturn
|
||||||
|
{
|
||||||
|
public bool Success { get; init; } = false;
|
||||||
|
public BreweryPost Brewery { get; init; }
|
||||||
|
public string Message { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
public BreweryServiceReturn(BreweryPost brewery)
|
||||||
|
{
|
||||||
|
Success = true;
|
||||||
|
Brewery = brewery;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BreweryServiceReturn(string message)
|
||||||
|
{
|
||||||
|
Success = false;
|
||||||
|
Brewery = default!;
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/Core/Service/Service.Breweries/Service.Breweries.csproj
Normal file
15
src/Core/Service/Service.Breweries/Service.Breweries.csproj
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Domain\Domain.Entities\Domain.Entities.csproj" />
|
||||||
|
<ProjectReference
|
||||||
|
Include="..\..\Infrastructure\Infrastructure.Repository\Infrastructure.Repository.csproj" />
|
||||||
|
<ProjectReference Include="..\..\API\API.Core\API.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\Service.Auth\Service.Auth.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
Reference in New Issue
Block a user