diff --git a/src/Printbase.Application/Products/Commands/CreateCategoryCommand.cs b/src/Printbase.Application/Products/Commands/CreateCategoryCommand.cs new file mode 100644 index 0000000..7fca7b7 --- /dev/null +++ b/src/Printbase.Application/Products/Commands/CreateCategoryCommand.cs @@ -0,0 +1,14 @@ +using MediatR; +using Printbase.Application.Products.Dtos; + +namespace Printbase.Application.Products.Commands; + +public class CreateCategoryCommand : IRequest +{ + public string Name { get; set; } = null!; + public string Description { get; set; } = null!; + public string? ImageUrl { get; set; } + public int SortOrder { get; set; } + public bool IsActive { get; set; } = true; + public Guid? ParentCategoryId { get; set; } +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/Commands/CreateProductCommand.cs b/src/Printbase.Application/Products/Commands/CreateProductCommand.cs new file mode 100644 index 0000000..fdff473 --- /dev/null +++ b/src/Printbase.Application/Products/Commands/CreateProductCommand.cs @@ -0,0 +1,15 @@ +using MediatR; +using Printbase.Application.Products.Dtos; + +namespace Printbase.Application.Products.Commands; + +public class CreateProductCommand : IRequest +{ + public string Name { get; set; } = null!; + public string? Description { get; set; } + public decimal BasePrice { get; set; } + public bool IsCustomizable { get; set; } + public bool IsActive { get; set; } = true; + public string? ImageUrl { get; set; } + public Guid? CategoryId { get; set; } +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/Commands/CreateProductVariantCommand.cs b/src/Printbase.Application/Products/Commands/CreateProductVariantCommand.cs new file mode 100644 index 0000000..8cb5756 --- /dev/null +++ b/src/Printbase.Application/Products/Commands/CreateProductVariantCommand.cs @@ -0,0 +1,16 @@ +using MediatR; +using Printbase.Application.Products.Dtos; + +namespace Printbase.Application.Products.Commands; + +public class CreateProductVariantCommand : IRequest +{ + public Guid ProductId { get; set; } + public string Size { get; set; } = null!; + public string? Color { get; set; } + public decimal Price { get; set; } + public string? ImageUrl { get; set; } + public string Sku { get; set; } = null!; + public int StockQuantity { get; set; } + public bool IsActive { get; set; } = true; +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/Commands/DeleteCategoryCommand.cs b/src/Printbase.Application/Products/Commands/DeleteCategoryCommand.cs new file mode 100644 index 0000000..237e724 --- /dev/null +++ b/src/Printbase.Application/Products/Commands/DeleteCategoryCommand.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace Printbase.Application.Products.Commands; + +public class DeleteCategoryCommand : IRequest +{ + public Guid Id { get; set; } +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/Commands/DeleteProductCommand.cs b/src/Printbase.Application/Products/Commands/DeleteProductCommand.cs new file mode 100644 index 0000000..6a1a08f --- /dev/null +++ b/src/Printbase.Application/Products/Commands/DeleteProductCommand.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace Printbase.Application.Products.Commands; + +public class DeleteProductCommand : IRequest +{ + public Guid Id { get; set; } +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/Commands/DeleteProductVariantCommand.cs b/src/Printbase.Application/Products/Commands/DeleteProductVariantCommand.cs new file mode 100644 index 0000000..d0ca4cc --- /dev/null +++ b/src/Printbase.Application/Products/Commands/DeleteProductVariantCommand.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace Printbase.Application.Products.Commands; + +public class DeleteProductVariantCommand : IRequest +{ + public Guid Id { get; set; } +} diff --git a/src/Printbase.Application/Products/Dtos/CategoryDto.cs b/src/Printbase.Application/Products/Dtos/CategoryDto.cs new file mode 100644 index 0000000..32127ec --- /dev/null +++ b/src/Printbase.Application/Products/Dtos/CategoryDto.cs @@ -0,0 +1,14 @@ +namespace Printbase.Application.Products.Dtos; + +public class CategoryDto +{ + public Guid Id { get; set; } + public string Name { get; set; } = null!; + public string Description { get; set; } = null!; + public string? ImageUrl { get; set; } + public int SortOrder { get; set; } + public bool IsActive { get; set; } + public Guid? ParentCategoryId { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime ModifiedAt { get; set; } +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/Dtos/PagedResultDto.cs b/src/Printbase.Application/Products/Dtos/PagedResultDto.cs new file mode 100644 index 0000000..775ddda --- /dev/null +++ b/src/Printbase.Application/Products/Dtos/PagedResultDto.cs @@ -0,0 +1,12 @@ +namespace Printbase.Application.Products.Dtos; + +public class PagedResultDto +{ + public IEnumerable Items { get; set; } = new List(); + public int TotalCount { get; set; } + public int PageNumber { get; set; } + public int PageSize { get; set; } + public int TotalPages => (int)Math.Ceiling((double)TotalCount / PageSize); + public bool HasPreviousPage => PageNumber > 1; + public bool HasNextPage => PageNumber < TotalPages; +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/Dtos/ProductDto.cs b/src/Printbase.Application/Products/Dtos/ProductDto.cs new file mode 100644 index 0000000..05f47eb --- /dev/null +++ b/src/Printbase.Application/Products/Dtos/ProductDto.cs @@ -0,0 +1,16 @@ +namespace Printbase.Application.Products.Dtos; + +public class ProductDto +{ + public Guid Id { get; set; } + public string Name { get; set; } = null!; + public string? Description { get; set; } + public decimal BasePrice { get; set; } + public bool IsCustomizable { get; set; } + public bool IsActive { get; set; } + public string? ImageUrl { get; set; } + public Guid? CategoryId { get; set; } + public CategoryDto? Category { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime ModifiedAt { get; set; } +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/Dtos/ProductVariantDto.cs b/src/Printbase.Application/Products/Dtos/ProductVariantDto.cs new file mode 100644 index 0000000..c5c2903 --- /dev/null +++ b/src/Printbase.Application/Products/Dtos/ProductVariantDto.cs @@ -0,0 +1,17 @@ +namespace Printbase.Application.Products.Dtos; + +public class ProductVariantDto +{ + public Guid Id { get; set; } + public Guid ProductId { get; set; } + public string Size { get; set; } = null!; + public string? Color { get; set; } + public decimal Price { get; set; } + public string? ImageUrl { get; set; } + public string Sku { get; set; } = null!; + public int StockQuantity { get; set; } + public bool IsActive { get; set; } + public ProductDto? Product { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime ModifiedAt { get; set; } +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/Handlers/CreateCategoryHandler.cs b/src/Printbase.Application/Products/Handlers/CreateCategoryHandler.cs new file mode 100644 index 0000000..05a60f4 --- /dev/null +++ b/src/Printbase.Application/Products/Handlers/CreateCategoryHandler.cs @@ -0,0 +1,48 @@ +using MediatR; +using Printbase.Application.Products.Commands; +using Printbase.Application.Products.Dtos; +using Printbase.Domain.Entities.Product; + +namespace Printbase.Application.Products.Handlers; + +public class CreateCategoryHandler(IUnitOfWork unitOfWork) : IRequestHandler +{ + public async Task Handle(CreateCategoryCommand request, CancellationToken cancellationToken) + { + await unitOfWork.BeginTransactionAsync(); + + try + { + var category = new Category + { + Name = request.Name, + Description = request.Description, + ImageUrl = request.ImageUrl, + SortOrder = request.SortOrder, + IsActive = request.IsActive, + ParentCategoryId = request.ParentCategoryId + }; + + var createdCategory = await unitOfWork.CategoryRepository.AddAsync(category, cancellationToken); + await unitOfWork.CommitTransactionAsync(); + + return new CategoryDto + { + Id = createdCategory.Id, + Name = createdCategory.Name, + Description = createdCategory.Description, + ImageUrl = createdCategory.ImageUrl, + SortOrder = createdCategory.SortOrder, + IsActive = createdCategory.IsActive, + ParentCategoryId = createdCategory.ParentCategoryId, + CreatedAt = createdCategory.CreatedAt, + ModifiedAt = createdCategory.ModifiedAt + }; + } + catch + { + await unitOfWork.RollbackTransactionAsync(); + throw; + } + } +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/Handlers/CreateProductHandler.cs b/src/Printbase.Application/Products/Handlers/CreateProductHandler.cs new file mode 100644 index 0000000..de8769c --- /dev/null +++ b/src/Printbase.Application/Products/Handlers/CreateProductHandler.cs @@ -0,0 +1,65 @@ +using MediatR; +using Printbase.Application.Products.Commands; +using Printbase.Application.Products.Dtos; +using Printbase.Domain.Entities.Product; + +namespace Printbase.Application.Products.Handlers; + +public class CreateProductHandler(IUnitOfWork unitOfWork) : IRequestHandler +{ + public async Task Handle(CreateProductCommand request, CancellationToken cancellationToken) + { + await unitOfWork.BeginTransactionAsync(); + + try + { + var product = new Product + { + Name = request.Name, + Description = request.Description, + BasePrice = request.BasePrice, + IsCustomizable = request.IsCustomizable, + IsActive = request.IsActive, + ImageUrl = request.ImageUrl, + CategoryId = request.CategoryId, + Category = null! + }; + + var createdProduct = await unitOfWork.ProductRepository.AddAsync(product, cancellationToken); + await unitOfWork.CommitTransactionAsync(); + + var categoryDto = new CategoryDto + { + Id = createdProduct.Category.Id, + Name = createdProduct.Category.Name, + Description = createdProduct.Category.Description, + ImageUrl = createdProduct.Category.ImageUrl, + SortOrder = createdProduct.Category.SortOrder, + IsActive = createdProduct.Category.IsActive, + ParentCategoryId = createdProduct.Category.ParentCategoryId, + CreatedAt = createdProduct.Category.CreatedAt, + ModifiedAt = createdProduct.Category.ModifiedAt + }; + + return new ProductDto + { + Id = createdProduct.Id, + Name = createdProduct.Name, + Description = createdProduct.Description, + BasePrice = createdProduct.BasePrice, + IsCustomizable = createdProduct.IsCustomizable, + IsActive = createdProduct.IsActive, + ImageUrl = createdProduct.ImageUrl, + CategoryId = createdProduct.CategoryId, + Category = categoryDto, + CreatedAt = createdProduct.CreatedAt, + ModifiedAt = createdProduct.ModifiedAt + }; + } + catch + { + await unitOfWork.RollbackTransactionAsync(); + throw; + } + } +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/Handlers/CreateProductVariantHandler.cs b/src/Printbase.Application/Products/Handlers/CreateProductVariantHandler.cs new file mode 100644 index 0000000..1e6f957 --- /dev/null +++ b/src/Printbase.Application/Products/Handlers/CreateProductVariantHandler.cs @@ -0,0 +1,54 @@ +using MediatR; +using Printbase.Application.Products.Commands; +using Printbase.Application.Products.Dtos; +using Printbase.Domain.Entities.Product; + +namespace Printbase.Application.Products.Handlers; + +public class CreateProductVariantHandler(IUnitOfWork unitOfWork) + : IRequestHandler +{ + public async Task Handle(CreateProductVariantCommand request, CancellationToken cancellationToken) + { + await unitOfWork.BeginTransactionAsync(); + + try + { + var productVariant = new ProductVariant + { + ProductId = request.ProductId, + Size = request.Size, + Color = request.Color, + Price = request.Price, + ImageUrl = request.ImageUrl, + Sku = request.Sku, + StockQuantity = request.StockQuantity, + IsActive = request.IsActive, + Product = null! + }; + + var createdVariant = await unitOfWork.ProductVariantRepository.AddAsync(productVariant, cancellationToken); + await unitOfWork.CommitTransactionAsync(); + + return new ProductVariantDto + { + Id = createdVariant.Id, + ProductId = createdVariant.ProductId, + Size = createdVariant.Size, + Color = createdVariant.Color, + Price = createdVariant.Price, + ImageUrl = createdVariant.ImageUrl, + Sku = createdVariant.Sku, + StockQuantity = createdVariant.StockQuantity, + IsActive = createdVariant.IsActive, + CreatedAt = createdVariant.CreatedAt, + ModifiedAt = createdVariant.ModifiedAt + }; + } + catch + { + await unitOfWork.RollbackTransactionAsync(); + throw; + } + } +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/Handlers/DeleteCategoryHandler.cs b/src/Printbase.Application/Products/Handlers/DeleteCategoryHandler.cs new file mode 100644 index 0000000..c295bf6 --- /dev/null +++ b/src/Printbase.Application/Products/Handlers/DeleteCategoryHandler.cs @@ -0,0 +1,31 @@ +using MediatR; +using Printbase.Application.Products.Commands; + +namespace Printbase.Application.Products.Handlers; + +public class DeleteCategoryHandler(IUnitOfWork unitOfWork) : IRequestHandler +{ + public async Task Handle(DeleteCategoryCommand request, CancellationToken cancellationToken) + { + await unitOfWork.BeginTransactionAsync(); + + try + { + var exists = await unitOfWork.CategoryRepository.ExistsAsync(request.Id, cancellationToken); + if (!exists) + { + await unitOfWork.RollbackTransactionAsync(); + return false; + } + + await unitOfWork.CategoryRepository.DeleteAsync(request.Id, cancellationToken); + await unitOfWork.CommitTransactionAsync(); + return true; + } + catch + { + await unitOfWork.RollbackTransactionAsync(); + throw; + } + } +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/Handlers/DeleteProductHandler.cs b/src/Printbase.Application/Products/Handlers/DeleteProductHandler.cs new file mode 100644 index 0000000..aa7798f --- /dev/null +++ b/src/Printbase.Application/Products/Handlers/DeleteProductHandler.cs @@ -0,0 +1,31 @@ +using MediatR; +using Printbase.Application.Products.Commands; + +namespace Printbase.Application.Products.Handlers; + +public class DeleteProductHandler(IUnitOfWork unitOfWork) : IRequestHandler +{ + public async Task Handle(DeleteProductCommand request, CancellationToken cancellationToken) + { + await unitOfWork.BeginTransactionAsync(); + + try + { + var exists = await unitOfWork.ProductRepository.ExistsAsync(request.Id, cancellationToken); + if (!exists) + { + await unitOfWork.RollbackTransactionAsync(); + return false; + } + + await unitOfWork.ProductRepository.DeleteAsync(request.Id, cancellationToken); + await unitOfWork.CommitTransactionAsync(); + return true; + } + catch + { + await unitOfWork.RollbackTransactionAsync(); + throw; + } + } +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/Handlers/DeleteProductVariantHandler.cs b/src/Printbase.Application/Products/Handlers/DeleteProductVariantHandler.cs new file mode 100644 index 0000000..917c91f --- /dev/null +++ b/src/Printbase.Application/Products/Handlers/DeleteProductVariantHandler.cs @@ -0,0 +1,31 @@ +using MediatR; +using Printbase.Application.Products.Commands; + +namespace Printbase.Application.Products.Handlers; + +public class DeleteProductVariantHandler(IUnitOfWork unitOfWork) : IRequestHandler +{ + public async Task Handle(DeleteProductVariantCommand request, CancellationToken cancellationToken) + { + await unitOfWork.BeginTransactionAsync(); + + try + { + var exists = await unitOfWork.ProductVariantRepository.ExistsAsync(request.Id, cancellationToken); + if (!exists) + { + await unitOfWork.RollbackTransactionAsync(); + return false; + } + + await unitOfWork.ProductVariantRepository.DeleteAsync(request.Id, cancellationToken); + await unitOfWork.CommitTransactionAsync(); + return true; + } + catch + { + await unitOfWork.RollbackTransactionAsync(); + throw; + } + } +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/Handlers/GetProductsHandler.cs b/src/Printbase.Application/Products/Handlers/GetProductsHandler.cs new file mode 100644 index 0000000..d933a13 --- /dev/null +++ b/src/Printbase.Application/Products/Handlers/GetProductsHandler.cs @@ -0,0 +1,47 @@ +using MediatR; +using Printbase.Application.Products.Dtos; +using Printbase.Application.Products.Queries; + +namespace Printbase.Application.Products.Handlers; + +public class GetProductsHandler(IUnitOfWork unitOfWork) : IRequestHandler> +{ + public async Task> Handle(GetProductsQuery request, CancellationToken cancellationToken) + { + var pagedResult = await unitOfWork.ProductRepository.GetPagedAsync(request.FilterParameters, cancellationToken); + + var productDtos = pagedResult.Items.Select(p => new ProductDto + { + Id = p.Id, + Name = p.Name, + Description = p.Description, + BasePrice = p.BasePrice, + IsCustomizable = p.IsCustomizable, + IsActive = p.IsActive, + ImageUrl = p.ImageUrl, + CategoryId = p.CategoryId, + Category = new CategoryDto + { + Id = p.Category.Id, + Name = p.Category.Name, + Description = p.Category.Description, + ImageUrl = p.Category.ImageUrl, + SortOrder = p.Category.SortOrder, + IsActive = p.Category.IsActive, + ParentCategoryId = p.Category.ParentCategoryId, + CreatedAt = p.Category.CreatedAt, + ModifiedAt = p.Category.ModifiedAt + }, + CreatedAt = p.CreatedAt, + ModifiedAt = p.ModifiedAt + }); + + return new PagedResultDto + { + Items = productDtos, + TotalCount = pagedResult.TotalCount, + PageNumber = pagedResult.PageNumber, + PageSize = pagedResult.PageSize + }; + } +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/Queries/GetProductsQuery.cs b/src/Printbase.Application/Products/Queries/GetProductsQuery.cs new file mode 100644 index 0000000..3912b59 --- /dev/null +++ b/src/Printbase.Application/Products/Queries/GetProductsQuery.cs @@ -0,0 +1,10 @@ +using MediatR; +using Printbase.Application.Products.Dtos; +using Printbase.Domain.Common.Models; + +namespace Printbase.Application.Products.Queries; + +public class GetProductsQuery : IRequest> +{ + public ProductFilterParameters FilterParameters { get; set; } = new(); +} \ No newline at end of file diff --git a/src/Printbase.WebApi/Printbase.WebApi.csproj b/src/Printbase.WebApi/Printbase.WebApi.csproj index 92ed393..df6999d 100644 --- a/src/Printbase.WebApi/Printbase.WebApi.csproj +++ b/src/Printbase.WebApi/Printbase.WebApi.csproj @@ -7,6 +7,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/Printbase.WebApi/Startup.cs b/src/Printbase.WebApi/Startup.cs index a0921e4..3775071 100644 --- a/src/Printbase.WebApi/Startup.cs +++ b/src/Printbase.WebApi/Startup.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; +using Printbase.Application.Products.Handlers; using Printbase.Domain.Entities.Users; using Printbase.Infrastructure.Database; @@ -16,6 +17,11 @@ public static class Startup builder.Configuration.GetConnectionString("DefaultConnection"), b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName))); + services.AddMediatR(cfg => + { + cfg.RegisterServicesFromAssembly(typeof(CreateProductHandler).Assembly); + }); + services.AddIdentity(options => { options.Password.RequireDigit = true;