From 8f565ff0afb38bb30c2d0969cd6bed16da0ff0f4 Mon Sep 17 00:00:00 2001 From: lumijiez <59575049+lumijiez@users.noreply.github.com> Date: Sun, 4 May 2025 01:22:10 +0300 Subject: [PATCH] Try to map everything beforehand, bad decision, checkpoint --- .../Common/Behaviors/LoggingBehavior.cs | 35 +++++++++ .../Common/Behaviors/ValidationBehavior.cs | 31 ++++++++ .../Mappings/ApplicationMappingProfile.cs | 72 +++++++++++++++++++ .../Common/Models/ApiResponse.cs | 43 +++++++++++ .../Printbase.Application.csproj | 12 +++- .../ProductGroup/ProductGroupDto.cs | 23 ++++++ .../ProductType/ProductTypeDto.cs | 23 ++++++ .../Products/ProductDto.cs | 60 ++++++++++++++++ src/Printbase.WebApi/Printbase.WebApi.csproj | 1 + src/Printbase.WebApi/Program.cs | 7 ++ 10 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 src/Printbase.Application/Common/Behaviors/LoggingBehavior.cs create mode 100644 src/Printbase.Application/Common/Behaviors/ValidationBehavior.cs create mode 100644 src/Printbase.Application/Common/Mappings/ApplicationMappingProfile.cs create mode 100644 src/Printbase.Application/Common/Models/ApiResponse.cs create mode 100644 src/Printbase.Application/ProductGroup/ProductGroupDto.cs create mode 100644 src/Printbase.Application/ProductType/ProductTypeDto.cs create mode 100644 src/Printbase.Application/Products/ProductDto.cs diff --git a/src/Printbase.Application/Common/Behaviors/LoggingBehavior.cs b/src/Printbase.Application/Common/Behaviors/LoggingBehavior.cs new file mode 100644 index 0000000..16017eb --- /dev/null +++ b/src/Printbase.Application/Common/Behaviors/LoggingBehavior.cs @@ -0,0 +1,35 @@ +using System.Diagnostics; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace Printbase.Application.Common.Behaviors; + +public class LoggingBehavior(ILogger> logger) + : IPipelineBehavior + where TRequest : IRequest +{ + public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) + { + var requestName = typeof(TRequest).Name; + + logger.LogInformation($"Handling {requestName}"); + + var stopwatch = Stopwatch.StartNew(); + + try + { + var response = await next(cancellationToken); + stopwatch.Stop(); + + logger.LogInformation($"Handling {requestName} in {stopwatch.ElapsedMilliseconds}ms"); + + return response; + } + catch (Exception ex) + { + stopwatch.Stop(); + logger.LogError(ex, $"Error handling {requestName} after {stopwatch.ElapsedMilliseconds}ms"); + throw; + } + } +} \ No newline at end of file diff --git a/src/Printbase.Application/Common/Behaviors/ValidationBehavior.cs b/src/Printbase.Application/Common/Behaviors/ValidationBehavior.cs new file mode 100644 index 0000000..d152073 --- /dev/null +++ b/src/Printbase.Application/Common/Behaviors/ValidationBehavior.cs @@ -0,0 +1,31 @@ +using FluentValidation; +using MediatR; +using ValidationException = System.ComponentModel.DataAnnotations.ValidationException; + +namespace Printbase.Application.Common.Behaviors; + +public class ValidationBehavior(IEnumerable> validators) + : IPipelineBehavior + where TRequest : IRequest +{ + public async Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) + { + if (!validators.Any()) + return await next(cancellationToken); + + var context = new ValidationContext(request); + + var validationResults = await Task.WhenAll( + validators.Select(v => v.ValidateAsync(context, cancellationToken))); + + var failures = validationResults + .SelectMany(r => r.Errors) + .Where(f => f != null) + .ToList(); + + if (failures.Count != 0) + throw new ValidationException(failures.ToString()); + + return await next(cancellationToken); + } +} \ No newline at end of file diff --git a/src/Printbase.Application/Common/Mappings/ApplicationMappingProfile.cs b/src/Printbase.Application/Common/Mappings/ApplicationMappingProfile.cs new file mode 100644 index 0000000..aaa2d2c --- /dev/null +++ b/src/Printbase.Application/Common/Mappings/ApplicationMappingProfile.cs @@ -0,0 +1,72 @@ +using AutoMapper; +using Printbase.Application.ProductGroup; +using Printbase.Application.Products; +using Printbase.Application.ProductType; +using Printbase.Domain.Entities.Products; + +namespace Printbase.Application.Common.Mappings; + +public class ApplicationMappingProfile : Profile +{ + public ApplicationMappingProfile() + { + CreateMap() + .ForMember(dest => dest.TypeName, opt => opt.Ignore()) + .ForMember(dest => dest.Variants, opt => opt.MapFrom(src => src.Variants)); + + CreateMap() + .ConstructUsing((src, ctx) => new Product( + Guid.NewGuid(), + src.Name, + src.TypeId, + src.Description, + src.Discount)) + .ForMember(dest => dest.Variants, opt => opt.Ignore()); + + CreateMap() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.Variants, opt => opt.Ignore()); + + CreateMap() + .ForMember(dest => dest.EffectivePrice, opt => opt.MapFrom(src => src.GetEffectivePrice(src.Discount))); + + CreateMap() + .ConstructUsing((src, ctx) => new ProductVariant( + Guid.NewGuid(), + Guid.Empty, + src.Price, + src.Color, + src.Size, + src.Discount, + src.Stock)); + + CreateMap() + .ForMember(dest => dest.ProductId, opt => opt.Ignore()); + + CreateMap() + .ForMember(dest => dest.GroupName, opt => opt.Ignore()); + + CreateMap() + .ConstructUsing((src, ctx) => new Domain.Entities.Products.ProductType( + Guid.NewGuid(), + src.Name, + src.GroupId, + src.Description)); + + CreateMap() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.GroupId, opt => opt.Ignore()); + + CreateMap(); + + CreateMap() + .ConstructUsing((src, ctx) => new Domain.Entities.Products.ProductGroup( + Guid.NewGuid(), + src.Name, + src.Description)); + + CreateMap() + .ForMember(dest => dest.Id, opt => opt.Ignore()) + .ForMember(dest => dest.Types, opt => opt.Ignore()); + } +} \ No newline at end of file diff --git a/src/Printbase.Application/Common/Models/ApiResponse.cs b/src/Printbase.Application/Common/Models/ApiResponse.cs new file mode 100644 index 0000000..d3fd7cc --- /dev/null +++ b/src/Printbase.Application/Common/Models/ApiResponse.cs @@ -0,0 +1,43 @@ +using System.Net; + +namespace Printbase.Application.Common.Models; + +public class ApiResponse +{ + public bool Succeeded { get; protected set; } + public string Message { get; protected set; } = string.Empty; + public T? Data { get; protected set; } + public IList Errors { get; protected set; } = new List(); + public HttpStatusCode StatusCode { get; protected set; } + + public static ApiResponse Success(T data, HttpStatusCode statusCode = HttpStatusCode.OK, string message = "") + { + return new ApiResponse + { + Succeeded = true, + Data = data, + StatusCode = statusCode, + Message = message + }; + } + + public static ApiResponse Fail(string error, HttpStatusCode statusCode = HttpStatusCode.BadRequest) + { + return new ApiResponse + { + Succeeded = false, + Errors = new List { error }, + StatusCode = statusCode + }; + } + + public static ApiResponse Fail(IList errors, HttpStatusCode statusCode = HttpStatusCode.BadRequest) + { + return new ApiResponse + { + Succeeded = false, + Errors = errors, + StatusCode = statusCode + }; + } +} \ No newline at end of file diff --git a/src/Printbase.Application/Printbase.Application.csproj b/src/Printbase.Application/Printbase.Application.csproj index 7d1a6dc..a3eb15f 100644 --- a/src/Printbase.Application/Printbase.Application.csproj +++ b/src/Printbase.Application/Printbase.Application.csproj @@ -8,7 +8,17 @@ - + + + + + + + + + + + diff --git a/src/Printbase.Application/ProductGroup/ProductGroupDto.cs b/src/Printbase.Application/ProductGroup/ProductGroupDto.cs new file mode 100644 index 0000000..96d9bab --- /dev/null +++ b/src/Printbase.Application/ProductGroup/ProductGroupDto.cs @@ -0,0 +1,23 @@ +using Printbase.Application.ProductType; + +namespace Printbase.Application.ProductGroup; + +public class ProductGroupDto +{ + public Guid Id { get; set; } + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public List Types { get; set; } = []; +} + +public class ProductGroupCreateDto +{ + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } +} + +public class ProductGroupUpdateDto +{ + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } +} \ No newline at end of file diff --git a/src/Printbase.Application/ProductType/ProductTypeDto.cs b/src/Printbase.Application/ProductType/ProductTypeDto.cs new file mode 100644 index 0000000..ae5ee58 --- /dev/null +++ b/src/Printbase.Application/ProductType/ProductTypeDto.cs @@ -0,0 +1,23 @@ +namespace Printbase.Application.ProductType; + +public class ProductTypeDto +{ + public Guid Id { get; set; } + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public Guid GroupId { get; set; } + public string GroupName { get; set; } = string.Empty; +} + +public class ProductTypeCreateDto +{ + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public Guid GroupId { get; set; } +} + +public class ProductTypeUpdateDto +{ + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } +} \ No newline at end of file diff --git a/src/Printbase.Application/Products/ProductDto.cs b/src/Printbase.Application/Products/ProductDto.cs new file mode 100644 index 0000000..ad2fd75 --- /dev/null +++ b/src/Printbase.Application/Products/ProductDto.cs @@ -0,0 +1,60 @@ +namespace Printbase.Application.Products; + +public class ProductDto +{ + public Guid Id { get; set; } + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public decimal? Discount { get; set; } + public Guid TypeId { get; set; } + public string TypeName { get; set; } = string.Empty; + public List Variants { get; set; } = new(); +} + +public class ProductVariantDto +{ + public Guid Id { get; set; } + public Guid ProductId { get; set; } + public string? Color { get; set; } + public string? Size { get; set; } + public decimal Price { get; set; } + public decimal? Discount { get; set; } + public int Stock { get; set; } + public decimal EffectivePrice { get; set; } +} + +public class ProductCreateDto +{ + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public decimal? Discount { get; set; } + public Guid TypeId { get; set; } + public List Variants { get; set; } = new(); +} + +public class ProductVariantCreateDto +{ + public string? Color { get; set; } + public string? Size { get; set; } + public decimal Price { get; set; } + public decimal? Discount { get; set; } + public int Stock { get; set; } +} + +public class ProductUpdateDto +{ + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } + public decimal? Discount { get; set; } + public Guid TypeId { get; set; } +} + +public class ProductVariantUpdateDto +{ + public Guid Id { get; set; } + public string? Color { get; set; } + public string? Size { get; set; } + public decimal Price { get; set; } + public decimal? Discount { get; set; } + public int Stock { get; set; } +} \ No newline at end of file diff --git a/src/Printbase.WebApi/Printbase.WebApi.csproj b/src/Printbase.WebApi/Printbase.WebApi.csproj index bc95296..817b7d5 100644 --- a/src/Printbase.WebApi/Printbase.WebApi.csproj +++ b/src/Printbase.WebApi/Printbase.WebApi.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Printbase.WebApi/Program.cs b/src/Printbase.WebApi/Program.cs index 3853a27..124f84f 100644 --- a/src/Printbase.WebApi/Program.cs +++ b/src/Printbase.WebApi/Program.cs @@ -1,4 +1,7 @@ +using System.Reflection; +using MediatR; using Microsoft.EntityFrameworkCore; +using Printbase.Application.Common.Behaviors; using Printbase.Domain.Repositories; using Printbase.Infrastructure.Database; using Printbase.Infrastructure.Mapping; @@ -15,6 +18,10 @@ services.AddDbContext(options => b => b.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName))); services.AddAutoMapper(typeof(ProductMappingProfile).Assembly); +services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly())); +services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationBehavior<,>)); +services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingBehavior<,>)); + services.AddScoped(); services.AddScoped(); services.AddScoped();