Add MediatR commands/queries/handlers and DTOs

This commit is contained in:
lumijiez
2025-05-27 13:13:56 +03:00
parent 89ac29c1fd
commit 6e59f46cf0
20 changed files with 452 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
using MediatR;
using Printbase.Application.Products.Dtos;
namespace Printbase.Application.Products.Commands;
public class CreateCategoryCommand : IRequest<CategoryDto>
{
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; }
}

View File

@@ -0,0 +1,15 @@
using MediatR;
using Printbase.Application.Products.Dtos;
namespace Printbase.Application.Products.Commands;
public class CreateProductCommand : IRequest<ProductDto>
{
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; }
}

View File

@@ -0,0 +1,16 @@
using MediatR;
using Printbase.Application.Products.Dtos;
namespace Printbase.Application.Products.Commands;
public class CreateProductVariantCommand : IRequest<ProductVariantDto>
{
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;
}

View File

@@ -0,0 +1,8 @@
using MediatR;
namespace Printbase.Application.Products.Commands;
public class DeleteCategoryCommand : IRequest<bool>
{
public Guid Id { get; set; }
}

View File

@@ -0,0 +1,8 @@
using MediatR;
namespace Printbase.Application.Products.Commands;
public class DeleteProductCommand : IRequest<bool>
{
public Guid Id { get; set; }
}

View File

@@ -0,0 +1,8 @@
using MediatR;
namespace Printbase.Application.Products.Commands;
public class DeleteProductVariantCommand : IRequest<bool>
{
public Guid Id { get; set; }
}

View File

@@ -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; }
}

View File

@@ -0,0 +1,12 @@
namespace Printbase.Application.Products.Dtos;
public class PagedResultDto<T>
{
public IEnumerable<T> Items { get; set; } = new List<T>();
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;
}

View File

@@ -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; }
}

View File

@@ -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; }
}

View File

@@ -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<CreateCategoryCommand, CategoryDto>
{
public async Task<CategoryDto> 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;
}
}
}

View File

@@ -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<CreateProductCommand, ProductDto>
{
public async Task<ProductDto> 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;
}
}
}

View File

@@ -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<CreateProductVariantCommand, ProductVariantDto>
{
public async Task<ProductVariantDto> 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;
}
}
}

View File

@@ -0,0 +1,31 @@
using MediatR;
using Printbase.Application.Products.Commands;
namespace Printbase.Application.Products.Handlers;
public class DeleteCategoryHandler(IUnitOfWork unitOfWork) : IRequestHandler<DeleteCategoryCommand, bool>
{
public async Task<bool> 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;
}
}
}

View File

@@ -0,0 +1,31 @@
using MediatR;
using Printbase.Application.Products.Commands;
namespace Printbase.Application.Products.Handlers;
public class DeleteProductHandler(IUnitOfWork unitOfWork) : IRequestHandler<DeleteProductCommand, bool>
{
public async Task<bool> 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;
}
}
}

View File

@@ -0,0 +1,31 @@
using MediatR;
using Printbase.Application.Products.Commands;
namespace Printbase.Application.Products.Handlers;
public class DeleteProductVariantHandler(IUnitOfWork unitOfWork) : IRequestHandler<DeleteProductVariantCommand, bool>
{
public async Task<bool> 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;
}
}
}

View File

@@ -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<GetProductsQuery, PagedResultDto<ProductDto>>
{
public async Task<PagedResultDto<ProductDto>> 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<ProductDto>
{
Items = productDtos,
TotalCount = pagedResult.TotalCount,
PageNumber = pagedResult.PageNumber,
PageSize = pagedResult.PageSize
};
}
}

View File

@@ -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<PagedResultDto<ProductDto>>
{
public ProductFilterParameters FilterParameters { get; set; } = new();
}

View File

@@ -7,6 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="MediatR" Version="12.5.0" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" /> <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4"> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>

View File

@@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Printbase.Application.Products.Handlers;
using Printbase.Domain.Entities.Users; using Printbase.Domain.Entities.Users;
using Printbase.Infrastructure.Database; using Printbase.Infrastructure.Database;
@@ -16,6 +17,11 @@ public static class Startup
builder.Configuration.GetConnectionString("DefaultConnection"), builder.Configuration.GetConnectionString("DefaultConnection"),
b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName))); b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName)));
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(CreateProductHandler).Assembly);
});
services.AddIdentity<ApplicationUser, ApplicationRole>(options => services.AddIdentity<ApplicationUser, ApplicationRole>(options =>
{ {
options.Password.RequireDigit = true; options.Password.RequireDigit = true;