Skip to content

category module all layers created #1123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/Category/Category.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\api\framework\Infrastructure\Infrastructure.csproj" />
</ItemGroup>

</Project>
45 changes: 45 additions & 0 deletions src/Category/CategoryModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using Carter;
using Category.Domain;
using Category.Features.Create.v1;
using Category.Features.Delete.v1;
using Category.Features.Get.v1;
using Category.Features.GetList.v1;
using Category.Features.Update.v1;
using Category.Persistence;
using FSH.Framework.Core.Persistence;
using FSH.Framework.Infrastructure.Persistence;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
namespace Category;

public static class CategoryModule
{

public class Endpoints : CarterModule
{
public override void AddRoutes(IEndpointRouteBuilder app)
{
var categoryItemGroup = app.MapGroup("categoryItems").WithTags("categoryItems");
categoryItemGroup.MapCategoryItemCreationEndpoint();
categoryItemGroup.MapGetCategoryItemEndpoint();
categoryItemGroup.MapGetCategoryItemListEndpoint();
categoryItemGroup.MapCategoryItemUpdationEndpoint();
categoryItemGroup.MapCategoryItemDeletionEndpoint();
}
}
public static WebApplicationBuilder RegisterCategoryItemServices(this WebApplicationBuilder builder)
{
ArgumentNullException.ThrowIfNull(builder);
builder.Services.BindDbContext<CategoryItemDbContext>();
builder.Services.AddScoped<IDbInitializer, CategoryItemDbInitializer>();
builder.Services.AddKeyedScoped<IRepository<CategoryItem>, CategoryItemRepository<CategoryItem>>("categoryItem");
builder.Services.AddKeyedScoped<IReadRepository<CategoryItem>, CategoryItemRepository<CategoryItem>>("categoryItem");
return builder;
}
public static WebApplication UseCategoryItemModule(this WebApplication app)
{
return app;
}
}
51 changes: 51 additions & 0 deletions src/Category/Domain/CategoryItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Category.Domain.Events;
using FSH.Framework.Core.Domain;
using FSH.Framework.Core.Domain.Contracts;

namespace Category.Domain;

public sealed class CategoryItem : AuditableEntity, IAggregateRoot
{
public string Name { get; private set; } = string.Empty;
public string Description { get; private set; } = string.Empty;

private CategoryItem() { }

private CategoryItem(string name, string description)
{
Name = name;
Description = description;
QueueDomainEvent(new CategoryItemCreated(Id, Name, Description));
}

public static CategoryItem Create(string name, string description) => new(name, description);

public CategoryItem Update(string? name, string? description)
{
bool isUpdated = false;

if (!string.IsNullOrWhiteSpace(name) && !string.Equals(Name, name, StringComparison.OrdinalIgnoreCase))
{
Name = name;
isUpdated = true;
}

if (!string.IsNullOrWhiteSpace(description) && !string.Equals(Description, description, StringComparison.OrdinalIgnoreCase))
{
Description = description;
isUpdated = true;
}

if (isUpdated)
{
QueueDomainEvent(new CategoryItemUpdated(this));
}

return this;
}
}
27 changes: 27 additions & 0 deletions src/Category/Domain/Events/CategoryItemCreated.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Category.Features.Get.v1;
using FSH.Framework.Core.Caching;
using FSH.Framework.Core.Domain.Events;
using MediatR;
using Microsoft.Extensions.Logging;

namespace Category.Domain.Events;

public record CategoryItemCreated(Guid Id, string Name, string Description) : DomainEvent;

public class CategoryItemCreatedEventHandler(
ILogger<CategoryItemCreatedEventHandler> logger,
ICacheService cache)
: INotificationHandler<CategoryItemCreated>
{
public async Task Handle(CategoryItemCreated notification, CancellationToken cancellationToken)
{
logger.LogInformation("handling categoryItem item created domain event..");
var cacheResponse = new GetCategoryItemResponse(notification.Id, notification.Name, notification.Description);
await cache.SetAsync($"categoryItem:{notification.Id}", cacheResponse, cancellationToken: cancellationToken);
}
}
27 changes: 27 additions & 0 deletions src/Category/Domain/Events/CategoryItemUpdated.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Category.Features.Get.v1;
using FSH.Framework.Core.Caching;
using FSH.Framework.Core.Domain.Events;
using MediatR;
using Microsoft.Extensions.Logging;

namespace Category.Domain.Events;

public record CategoryItemUpdated(CategoryItem item) : DomainEvent;

public class CategoryItemUpdatedEventHandler(
ILogger<CategoryItemUpdatedEventHandler> logger,
ICacheService cache)
: INotificationHandler<CategoryItemUpdated>
{
public async Task Handle(CategoryItemUpdated notification, CancellationToken cancellationToken)
{
logger.LogInformation("handling CategoryItem update domain event..");
var cacheResponse = new GetCategoryItemResponse(notification.item.Id, notification.item.Name, notification.item.Description );
await cache.SetAsync($"categoryItem:{notification.item.Id}", cacheResponse, cancellationToken: cancellationToken);
}
}
16 changes: 16 additions & 0 deletions src/Category/Exceptions/CategoryItemNotFoundException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FSH.Framework.Core.Exceptions;

namespace Category.Exceptions;

internal sealed class CategoryItemNotFoundException : NotFoundException
{
public CategoryItemNotFoundException(Guid id)
: base($"category item with id {id} not found")
{
}
}
15 changes: 15 additions & 0 deletions src/Category/Features/Create/v1/CreateCategoryItemCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MediatR;

namespace Category.Features.Create.v1;

public record CreateCategoryItemCommand(
[property: DefaultValue("Hello World!")] string Name,
[property: DefaultValue("Important Description.")] string Description) : IRequest<CreateCategoryItemResponse>;


32 changes: 32 additions & 0 deletions src/Category/Features/Create/v1/CreateCategoryItemEndPoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Asp.Versioning;
using FSH.Framework.Infrastructure.Auth.Policy;
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;

namespace Category.Features.Create.v1;

public static class CreateCategoryItemEndPoint
{
internal static RouteHandlerBuilder MapCategoryItemCreationEndpoint(this IEndpointRouteBuilder endpoints)
{
return endpoints.MapPost("/", async (CreateCategoryItemCommand request, ISender mediator) =>
{
var response = await mediator.Send(request);
return Results.CreatedAtRoute(nameof(CreateCategoryItemEndPoint), new { id = response.Id }, response);
})
.WithName(nameof(CreateCategoryItemEndPoint))
.WithSummary("Creates a CategoryItem item")
.WithDescription("Creates a CategoryItem item")
.Produces<CreateCategoryItemResponse>(StatusCodes.Status201Created)
.RequirePermission("Permissions.CategoryItems.Create")
.MapToApiVersion(new ApiVersion(1, 0));

}
}
28 changes: 28 additions & 0 deletions src/Category/Features/Create/v1/CreateCategoryItemHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Category.Domain;
using FSH.Framework.Core.Persistence;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Category.Features.Create.v1;

public sealed class CreateCategoryItemHandler(
ILogger<CreateCategoryItemHandler> logger,
[FromKeyedServices("categoryItem")] IRepository<CategoryItem> repository)
: IRequestHandler<CreateCategoryItemCommand, CreateCategoryItemResponse>
{
public async Task<CreateCategoryItemResponse> Handle(CreateCategoryItemCommand request, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);
var item = CategoryItem.Create(request.Name, request.Description);
await repository.AddAsync(item, cancellationToken).ConfigureAwait(false);
await repository.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
logger.LogInformation("CategoryItem item created {CategoryItemId}", item.Id);
return new CreateCategoryItemResponse(item.Id);
}
}
9 changes: 9 additions & 0 deletions src/Category/Features/Create/v1/CreateCategoryItemResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Category.Features.Create.v1;

public record CreateCategoryItemResponse(Guid? Id);
18 changes: 18 additions & 0 deletions src/Category/Features/Create/v1/CreateCategoryItemValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Category.Persistence;
using FluentValidation;

namespace Category.Features.Create.v1;

public class CreateCategoryItemValidator : AbstractValidator<CreateCategoryItemCommand>
{
public CreateCategoryItemValidator(CategoryItemDbContext context)
{
RuleFor(p => p.Name).NotEmpty();
RuleFor(p => p.Description).NotEmpty();
}
}
14 changes: 14 additions & 0 deletions src/Category/Features/Delete/v1/DeleteCategoryItemCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MediatR;

namespace Category.Features.Delete.v1;

public sealed record DeleteCategoryItemCommand(
Guid Id) : IRequest;



33 changes: 33 additions & 0 deletions src/Category/Features/Delete/v1/DeleteCategoryItemEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Asp.Versioning;
using FSH.Framework.Infrastructure.Auth.Policy;
using MediatR;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;

namespace Category.Features.Delete.v1;

public static class DeleteCategoryItemEndpoint
{
internal static RouteHandlerBuilder MapCategoryItemDeletionEndpoint(this IEndpointRouteBuilder endpoints)
{
return endpoints
.MapDelete("/{id:guid}", async (Guid id, ISender mediator) =>
{
await mediator.Send(new DeleteCategoryItemCommand(id));
return Results.NoContent();
})
.WithName(nameof(DeleteCategoryItemEndpoint))
.WithSummary("Deletes a category item")
.WithDescription("Deleted a category item")
.Produces(StatusCodes.Status204NoContent)
.RequirePermission("Permissions.CategoryItems.Delete")
.MapToApiVersion(new ApiVersion(1, 0));

}
}
28 changes: 28 additions & 0 deletions src/Category/Features/Delete/v1/DeleteCategoryItemHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Category.Domain;
using Category.Exceptions;
using FSH.Framework.Core.Persistence;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Category.Features.Delete.v1;

public sealed class DeleteCategoryItemHandler(
ILogger<DeleteCategoryItemHandler> logger,
[FromKeyedServices("categoryItem")] IRepository<CategoryItem> repository)
: IRequestHandler<DeleteCategoryItemCommand>
{
public async Task Handle(DeleteCategoryItemCommand request, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request);
var categoryItem = await repository.GetByIdAsync(request.Id, cancellationToken);
_ = categoryItem ?? throw new CategoryItemNotFoundException(request.Id);
await repository.DeleteAsync(categoryItem, cancellationToken);
logger.LogInformation("categoryItem with id : {CategoryItemId} deleted", categoryItem.Id);
}
}
Loading