Skip to content

Commit

Permalink
Catalog service has been created for Task 2.
Browse files Browse the repository at this point in the history
  • Loading branch information
JeffHolla committed Oct 7, 2024
1 parent 1b60b69 commit d1b93db
Show file tree
Hide file tree
Showing 63 changed files with 2,362 additions and 0 deletions.
385 changes: 385 additions & 0 deletions CatalogService/.editorconfig

Large diffs are not rendered by default.

481 changes: 481 additions & 0 deletions CatalogService/.gitignore

Large diffs are not rendered by default.

Empty file removed CatalogService/.gitkeep
Empty file.
90 changes: 90 additions & 0 deletions CatalogService/CatalogService.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Domain", "src\Domain\Domain.csproj", "{C7E89A3E-A631-4760-8D61-BD1EAB1C4E69}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application", "src\Application\Application.csproj", "{34C0FACD-F3D9-400C-8945-554DD6B0819A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure", "src\Infrastructure\Infrastructure.csproj", "{117DA02F-5274-4565-ACC6-DA9B6E568B09}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{6ED356A7-8B47-4613-AD01-C85CF28491BD}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{664D406C-2F83-48F0-BFC3-408D5CB53C65}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application.UnitTests", "tests\Application.UnitTests\Application.UnitTests.csproj", "{DEFF4009-1FAB-4392-80B6-707E2DC5C00B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Domain.UnitTests", "tests\Domain.UnitTests\Domain.UnitTests.csproj", "{DC37FD87-552C-4613-9F16-1537CA522898}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E2DA20AA-28D1-455C-BF50-C49A8F831633}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
Directory.Build.props = Directory.Build.props
Directory.Packages.props = Directory.Packages.props
global.json = global.json
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Web", "src\Web\Web.csproj", "{4E4EE20C-F06A-4A1B-851F-C5577796941C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Application.FunctionalTests", "tests\Application.FunctionalTests\Application.FunctionalTests.csproj", "{EA6127A5-94C9-4C31-AD11-E6811B92B520}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Infrastructure.IntegrationTests", "tests\Infrastructure.IntegrationTests\Infrastructure.IntegrationTests.csproj", "{01FA6786-921D-4CE8-8C50-4FDA66C9477D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C7E89A3E-A631-4760-8D61-BD1EAB1C4E69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C7E89A3E-A631-4760-8D61-BD1EAB1C4E69}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C7E89A3E-A631-4760-8D61-BD1EAB1C4E69}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C7E89A3E-A631-4760-8D61-BD1EAB1C4E69}.Release|Any CPU.Build.0 = Release|Any CPU
{34C0FACD-F3D9-400C-8945-554DD6B0819A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{34C0FACD-F3D9-400C-8945-554DD6B0819A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{34C0FACD-F3D9-400C-8945-554DD6B0819A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{34C0FACD-F3D9-400C-8945-554DD6B0819A}.Release|Any CPU.Build.0 = Release|Any CPU
{117DA02F-5274-4565-ACC6-DA9B6E568B09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{117DA02F-5274-4565-ACC6-DA9B6E568B09}.Debug|Any CPU.Build.0 = Debug|Any CPU
{117DA02F-5274-4565-ACC6-DA9B6E568B09}.Release|Any CPU.ActiveCfg = Release|Any CPU
{117DA02F-5274-4565-ACC6-DA9B6E568B09}.Release|Any CPU.Build.0 = Release|Any CPU
{DEFF4009-1FAB-4392-80B6-707E2DC5C00B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DEFF4009-1FAB-4392-80B6-707E2DC5C00B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DEFF4009-1FAB-4392-80B6-707E2DC5C00B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DEFF4009-1FAB-4392-80B6-707E2DC5C00B}.Release|Any CPU.Build.0 = Release|Any CPU
{DC37FD87-552C-4613-9F16-1537CA522898}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DC37FD87-552C-4613-9F16-1537CA522898}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DC37FD87-552C-4613-9F16-1537CA522898}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DC37FD87-552C-4613-9F16-1537CA522898}.Release|Any CPU.Build.0 = Release|Any CPU
{4E4EE20C-F06A-4A1B-851F-C5577796941C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4E4EE20C-F06A-4A1B-851F-C5577796941C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4E4EE20C-F06A-4A1B-851F-C5577796941C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4E4EE20C-F06A-4A1B-851F-C5577796941C}.Release|Any CPU.Build.0 = Release|Any CPU
{EA6127A5-94C9-4C31-AD11-E6811B92B520}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EA6127A5-94C9-4C31-AD11-E6811B92B520}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EA6127A5-94C9-4C31-AD11-E6811B92B520}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EA6127A5-94C9-4C31-AD11-E6811B92B520}.Release|Any CPU.Build.0 = Release|Any CPU
{01FA6786-921D-4CE8-8C50-4FDA66C9477D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{01FA6786-921D-4CE8-8C50-4FDA66C9477D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{01FA6786-921D-4CE8-8C50-4FDA66C9477D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{01FA6786-921D-4CE8-8C50-4FDA66C9477D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{C7E89A3E-A631-4760-8D61-BD1EAB1C4E69} = {6ED356A7-8B47-4613-AD01-C85CF28491BD}
{34C0FACD-F3D9-400C-8945-554DD6B0819A} = {6ED356A7-8B47-4613-AD01-C85CF28491BD}
{117DA02F-5274-4565-ACC6-DA9B6E568B09} = {6ED356A7-8B47-4613-AD01-C85CF28491BD}
{DEFF4009-1FAB-4392-80B6-707E2DC5C00B} = {664D406C-2F83-48F0-BFC3-408D5CB53C65}
{DC37FD87-552C-4613-9F16-1537CA522898} = {664D406C-2F83-48F0-BFC3-408D5CB53C65}
{4E4EE20C-F06A-4A1B-851F-C5577796941C} = {6ED356A7-8B47-4613-AD01-C85CF28491BD}
{EA6127A5-94C9-4C31-AD11-E6811B92B520} = {664D406C-2F83-48F0-BFC3-408D5CB53C65}
{01FA6786-921D-4CE8-8C50-4FDA66C9477D} = {664D406C-2F83-48F0-BFC3-408D5CB53C65}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3CB609D9-5D54-4C11-A371-DAAC8B74E430}
EndGlobalSection
EndGlobal
10 changes: 10 additions & 0 deletions CatalogService/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!-- See https://aka.ms/dotnet/msbuild/customize for more details on customizing your build -->
<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<!-- <ArtifactsPath>$(MSBuildThisFileDirectory)artifacts</ArtifactsPath> -->
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>annotations</Nullable>
</PropertyGroup>
</Project>
30 changes: 30 additions & 0 deletions CatalogService/Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!-- For more info on central package management go to https://devblogs.microsoft.com/nuget/introducing-central-package-management/ -->
<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="AutoMapper" Version="13.0.1" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="FluentAssertions" Version="6.12.0" />
<PackageVersion Include="FluentValidation.AspNetCore" Version="11.3.0" />
<PackageVersion Include="FluentValidation.DependencyInjectionExtensions" Version="11.9.2" />
<PackageVersion Include="MediatR" Version="12.4.0" />
<PackageVersion Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.8" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="8.0.8" />
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.8" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.8" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.8" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="8.0.0" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.0" />
<PackageVersion Include="Moq" Version="4.20.70" />
<PackageVersion Include="nunit" Version="3.14.0" />
<PackageVersion Include="NUnit.Analyzers" Version="3.9.0" />
<PackageVersion Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageVersion Include="Respawn" Version="6.2.1" />
<PackageVersion Include="Testcontainers.MsSql" Version="3.9.0" />
</ItemGroup>
</Project>
6 changes: 6 additions & 0 deletions CatalogService/global.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"sdk": {
"version": "8.0.100",
"rollForward": "latestFeature"
}
}
18 changes: 18 additions & 0 deletions CatalogService/src/Application/Application.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<RootNamespace>CatalogService.Application</RootNamespace>
<AssemblyName>CatalogService.Application</AssemblyName>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" />
<PackageReference Include="Microsoft.EntityFrameworkCore" />
<PackageReference Include="MediatR" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Domain\Domain.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.Extensions.Logging;

namespace CatalogService.Application.Common.Behaviours;

public class UnhandledExceptionBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse> where TRequest : notnull
{
private readonly ILogger<TRequest> _logger;

public UnhandledExceptionBehaviour(ILogger<TRequest> logger)
{
_logger = logger;
}

public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
try
{
return await next();
}
catch (Exception ex)
{
var requestName = typeof(TRequest).Name;

_logger.LogError(ex, "CatalogService Request: Unhandled Exception for Request {Name} {@Request}", requestName, request);

throw;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using ValidationException = CatalogService.Application.Common.Exceptions.ValidationException;

namespace CatalogService.Application.Common.Behaviours;

public class ValidationBehaviour<TRequest, TResponse> : IPipelineBehavior<TRequest, TResponse>
where TRequest : notnull
{
private readonly IEnumerable<IValidator<TRequest>> _validators;

public ValidationBehaviour(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}

public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
if (_validators.Any())
{
var context = new ValidationContext<TRequest>(request);

var validationResults = await Task.WhenAll(
_validators.Select(validator =>
validator.ValidateAsync(context, cancellationToken)));

var failures = validationResults
.Where(result => result.Errors.Any())
.SelectMany(failure => failure.Errors)
.ToList();

if (failures.Count != 0)
throw new ValidationException(failures);
}
return await next();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using FluentValidation.Results;

namespace CatalogService.Application.Common.Exceptions;

public class ValidationException : Exception
{
public ValidationException()
: base("One or more validation failures have occurred.")
{
Errors = new Dictionary<string, string[]>();
}

public ValidationException(IEnumerable<ValidationFailure> failures)
: this()
{
Errors = failures
.GroupBy(e => e.PropertyName, e => e.ErrorMessage)
.ToDictionary(failureGroup => failureGroup.Key, failureGroup => failureGroup.ToArray());
}

public IDictionary<string, string[]> Errors { get; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using CatalogService.Domain.Entities;

namespace CatalogService.Application.Common.Interfaces;

public interface IApplicationDbContext
{
DbSet<Product> Products { get; }
DbSet<Category> Categories { get; }


Task<int> SaveChangesAsync(CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using CatalogService.Domain.Entities;

namespace CatalogService.Application.Common.Interfaces;

public interface IReadOnlyApplicationDbContext
{
IQueryable<Product> Products { get; }
IQueryable<Category> Categories { get; }
}
16 changes: 16 additions & 0 deletions CatalogService/src/Application/Common/Models/ListResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace CatalogService.Application.Common.Models;

public class ListResponse<TResult>
{
public IEnumerable<TResult> Results { get; set; }

public ListResponse()
{
Results = [];
}

public ListResponse(IEnumerable<TResult> results)
{
Results = results;
}
}
20 changes: 20 additions & 0 deletions CatalogService/src/Application/DependencyInjection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Reflection;
using CatalogService.Application.Common.Behaviours;

namespace Microsoft.Extensions.DependencyInjection;

public static class DependencyInjection
{
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
{
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());

services.AddMediatR(cfg => {
cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(UnhandledExceptionBehaviour<,>));
cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(ValidationBehaviour<,>));
});

return services;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace CatalogService.Application.Features.CategoryHandlers.Command.AddCategory;

public class UpdateCategoryCommandValidator : AbstractValidator<AddCategoryCommand>
{
public UpdateCategoryCommandValidator()
{
RuleFor(category => category.Name)
.MaximumLength(50)
.NotEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using CatalogService.Application.Common.Interfaces;
using CatalogService.Domain.Entities;

namespace CatalogService.Application.Features.CategoryHandlers.Command.AddCategory;

public class AddCategoryCommand : IRequest
{
public string Name { get; init; }
public string? Image { get; init; }
public Category? Parent { get; init; }
}

public class UpdateCategoryHandler : IRequestHandler<AddCategoryCommand>
{
private readonly IApplicationDbContext _context;

public UpdateCategoryHandler(IApplicationDbContext context)
{
_context = context;
}

// Validation has been executed in MediatR Behaviours
public async Task Handle(AddCategoryCommand request, CancellationToken cancellationToken)
{
var newCategory = new Category
{
Name = request.Name,
Image = request.Image,
Parent = request.Parent
};

_context.Categories.Add(newCategory);

await _context.SaveChangesAsync(cancellationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using CatalogService.Application.Common.Interfaces;

namespace CatalogService.Application.Features.CategoryHandlers.Command.DeleteCategory;

public class DeleteCategoryCommand : IRequest
{
public int CategoryId { get; init; }
}

public class DeleteProductHandler : IRequestHandler<DeleteCategoryCommand>
{
private readonly IApplicationDbContext _context;

public DeleteProductHandler(IApplicationDbContext context)
{
_context = context;
}

public async Task Handle(DeleteCategoryCommand request, CancellationToken cancellationToken)
{
var categoryId = request.CategoryId;
var foundCategory = await _context.Categories.FirstOrDefaultAsync(category => category.Id == categoryId, cancellationToken);

if (foundCategory is null)
{
throw new KeyNotFoundException($"Category with id '{request.CategoryId}' was not found.");
}

_context.Categories.Remove(foundCategory);
await _context.SaveChangesAsync(cancellationToken);
}
}
Loading

0 comments on commit d1b93db

Please sign in to comment.