feat : add validators for commands , add filters in products query , add version 0.9.16.23

release
Amir Hossein Khademi 2024-02-24 19:34:53 +03:30
parent 75d53a7f76
commit 6ac4024ea9
30 changed files with 479 additions and 6 deletions

View File

@ -1 +1 @@
0.8.15.22
0.9.16.23

View File

@ -6,8 +6,8 @@
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<AssemblyVersion>0.8.15.22</AssemblyVersion>
<FileVersion>0.8.15.22</FileVersion>
<AssemblyVersion>0.9.16.23</AssemblyVersion>
<FileVersion>0.9.16.23</FileVersion>
</PropertyGroup>
<ItemGroup>

View File

@ -1,3 +1,5 @@
using NetinaShop.Repository.Behaviors;
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.UseSerilog();
@ -71,16 +73,19 @@ builder.Host.ConfigureContainer<ContainerBuilder>(builder =>
builder.RegisterMediatR(MediatRConfigurationBuilder
.Create(typeof(RepositoryConfig).Assembly)
.WithCustomPipelineBehavior(typeof(ValidationBehavior<,>))
.WithAllOpenGenericHandlerTypesRegistered()
.Build());
builder.RegisterMediatR(MediatRConfigurationBuilder
.Create(typeof(CoreConfig).Assembly)
.WithCustomPipelineBehavior(typeof(ValidationBehavior<,>))
.WithAllOpenGenericHandlerTypesRegistered()
.Build());
builder.RegisterMediatR(MediatRConfigurationBuilder
.Create(typeof(DomainConfig).Assembly)
.WithCustomPipelineBehavior(typeof(ValidationBehavior<,>))
.WithAllOpenGenericHandlerTypesRegistered()
.Build());
});

View File

@ -0,0 +1,20 @@
namespace NetinaShop.Common.Models.Exception;
public class ValidationException : System.Exception
{
public ValidationException() : base("Validation has been failed")
{
}
public ValidationException(params ValidationError[] validationErrors) : base($"{string.Join(",", validationErrors.Select(v => v.ErrorMessage))}")
{
}
public ValidationException(List<ValidationError> validationErrors) : base($"{string.Join(",", validationErrors.Select(v => v.ErrorMessage))}")
{
}
}
public sealed record ValidationError(string PropertyName, string ErrorMessage);

View File

@ -0,0 +1,10 @@
namespace NetinaShop.Domain.Dtos.FilterDtos;
public class BaseFilterDto
{
public string Name { get; set; } = string.Empty;
public string Type { get; set; } = string.Empty;
public string QueryName { get; set; } = string.Empty;
public QueryFilterType QueryType { get; set; }
public List<FilterOptionDto> Options { get; set; } = new List<FilterOptionDto>();
}

View File

@ -0,0 +1,13 @@
namespace NetinaShop.Domain.Dtos.FilterDtos;
public class ExpressDeliveryFilter : BaseFilterDto
{
public ExpressDeliveryFilter()
{
Name = "ارسال سریع";
QueryName = "expressDelivery";
Type = "checkbox";
QueryType = QueryFilterType.Bool;
Options.Add(new FilterOptionDto{Id = 0,Title = "فقط ارسال سریع"});
}
}

View File

@ -0,0 +1,7 @@
namespace NetinaShop.Domain.Dtos.FilterDtos;
public class FilterOptionDto
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
}

View File

@ -0,0 +1,15 @@
namespace NetinaShop.Domain.Dtos.FilterDtos;
public class PriceFilterDto : BaseFilterDto
{
public PriceFilterDto()
{
Name = "قیمت";
QueryName = "minimumPrice";
Type = "slider";
QueryType = QueryFilterType.Int;
}
public double MinimumValue { get; set; }
public double MaximumValue { get; set; }
}

View File

@ -1,8 +1,18 @@
namespace NetinaShop.Domain.Dtos.ResponseDtos;
using NetinaShop.Domain.Dtos.FilterDtos;
namespace NetinaShop.Domain.Dtos.ResponseDtos;
public class GetProductsResponseDto
{
public List<ProductSDto> Products { get; set; } = new List<ProductSDto>();
public PagerResponseDto Pager { get; set; } = new PagerResponseDto();
public FilterResponseDto Filters { get; set; } = new FilterResponseDto();
}
public class FilterResponseDto
{
public PriceFilterDto Price { get; set; } = new PriceFilterDto();
public ExpressDeliveryFilter ExpressDelivery { get; set; } = new ExpressDeliveryFilter();
}

View File

@ -0,0 +1,9 @@
namespace NetinaShop.Domain.Enums;
public enum QueryFilterType
{
Int = 0,
String = 1,
Guid = 2,
Bool = 3,
}

View File

@ -36,6 +36,7 @@
<ItemGroup>
<Folder Include="Dtos\FilterDtos\" />
<Folder Include="Entities\StorageFiles\" />
<Folder Include="Models\Settings\" />
</ItemGroup>

View File

@ -0,0 +1,34 @@
using FluentValidation;
using ValidationException = NetinaShop.Common.Models.Exception.ValidationException;
namespace NetinaShop.Repository.Behaviors;
public class ValidationBehavior <TRequest,TResponse> : IPipelineBehavior<TRequest,TResponse> where TRequest : notnull
{
private readonly IEnumerable<IValidator<TRequest>> _validators;
public ValidationBehavior(IEnumerable<IValidator<TRequest>> validators)
{
_validators = validators;
}
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken cancellationToken)
{
var context = new ValidationContext<TRequest>(request);
List<ValidationError> errors = new List<ValidationError>();
foreach (IValidator<TRequest> validator in _validators)
{
var result = await validator.ValidateAsync(context, cancellationToken);
if (!result.IsValid)
errors.AddRange(result.Errors.Select(v => new ValidationError(v.PropertyName, v.ErrorMessage)).Distinct());
}
if (errors.Any())
{
throw new ValidationException(errors);
}
var response = await next();
return response;
}
}

View File

@ -17,6 +17,7 @@ public class CreateAddressCommandHandler : IRequestHandler<CreateAddressCommand,
if (!Guid.TryParse(_currentUserService.UserId, out Guid userId))
throw new AppException("User id wrong", ApiResultStatusCode.BadRequest);
var ent = UserAddress.Create(request.Address, request.PostalCode, request.ReceiverFullName,
request.ReceiverPhoneNumber, request.LocationLat, request.LocationLong, request.Province, request.City,
request.Plaque, request.BuildingUnit, userId);

View File

@ -0,0 +1,19 @@
using FluentValidation;
namespace NetinaShop.Repository.Handlers.Brands.Validators;
public class CreateBrandCommandValidator : AbstractValidator<CreateBrandCommand>
{
public CreateBrandCommandValidator()
{
RuleFor(r => r.PersianName)
.NotNull()
.NotEmpty()
.WithMessage("نام فارسی برند را وارد کنید");
RuleFor(r => r.EnglishName)
.NotNull()
.NotEmpty()
.WithMessage("نام انگلیسی برند را وارد کنید");
}
}

View File

@ -0,0 +1,19 @@
using FluentValidation;
namespace NetinaShop.Repository.Handlers.Brands.Validators;
public class UpdateBrandCommandValidator : AbstractValidator<UpdateBrandCommand>
{
public UpdateBrandCommandValidator()
{
RuleFor(r => r.PersianName)
.NotNull()
.NotEmpty()
.WithMessage("نام فارسی برند را وارد کنید");
RuleFor(r => r.EnglishName)
.NotNull()
.NotEmpty()
.WithMessage("نام انگلیسی برند را وارد کنید");
}
}

View File

@ -0,0 +1,10 @@
using FluentValidation;
namespace NetinaShop.Repository.Handlers.Complexes.Validators;
public class CreateOrUpdateComplexCommandValidator : AbstractValidator<CreateOrUpdateComplexCommand>
{
public CreateOrUpdateComplexCommandValidator()
{
}
}

View File

@ -0,0 +1,39 @@
using FluentValidation;
namespace NetinaShop.Repository.Handlers.Discounts.Validators;
public class CreateDiscountCommandValidator : AbstractValidator<CreateDiscountCommand>
{
public CreateDiscountCommandValidator()
{
RuleFor(d=>d.Type)
.Must((command, type) =>
{
if (type == DiscountType.Category)
{
if (command.CategoryId == default)
return false;
else
return true;
}
return true;
})
.WithMessage("برای تخفیف برروی دسته ها باید دسته مورد نظر را وارد کنید");
RuleFor(d => d.Type)
.Must((command, type) =>
{
if (type == DiscountType.Product)
{
if (command.ProductId == default)
return false;
else
return true;
}
return true;
})
.WithMessage("برای تخفیف برروی کالای خاص باید کالای مورد نظر را وارد کنید");
}
}

View File

@ -0,0 +1,39 @@
using FluentValidation;
namespace NetinaShop.Repository.Handlers.Discounts.Validators;
public class UpdateDiscountCommandValidator : AbstractValidator<CreateDiscountCommand>
{
public UpdateDiscountCommandValidator()
{
RuleFor(d => d.Type)
.Must((command, type) =>
{
if (type == DiscountType.Category)
{
if (command.CategoryId == default)
return false;
else
return true;
}
return true;
})
.WithMessage("برای تخفیف برروی دسته ها باید دسته مورد نظر را وارد کنید");
RuleFor(d => d.Type)
.Must((command, type) =>
{
if (type == DiscountType.Product)
{
if (command.ProductId == default)
return false;
else
return true;
}
return true;
})
.WithMessage("برای تخفیف برروی کالای خاص باید کالای مورد نظر را وارد کنید");
}
}

View File

@ -0,0 +1,28 @@
using FluentValidation;
using Microsoft.IdentityModel.Tokens;
namespace NetinaShop.Repository.Handlers.Newsletters.Validators;
public class CreateNewsletterMemberCommandValidator : AbstractValidator<CreateNewsletterMemberCommand>
{
public CreateNewsletterMemberCommandValidator()
{
RuleFor(command => command)
.Must(command =>
{
if (command.PhoneNumber.IsNullOrEmpty() && command.Email.IsNullOrEmpty())
return false;
return true;
})
.WithMessage("شماره تلفن یا ایمیل را باید وارد کنید");
RuleFor(c=>c.PhoneNumber)
.Must(phoneNumber =>
{
if(phoneNumber.IsNullOrEmpty()) return true;
return PhoneNumberExtensions.CheckPhoneNumber(phoneNumber);
})
.WithMessage("شماره تلفن ارسالی اشتباه است");
}
}

View File

@ -10,6 +10,7 @@ public class CreateProductCategoryCommandHandler : IRequestHandler<CreateProduct
{
_repositoryWrapper = repositoryWrapper;
}
public async Task<ProductCategoryLDto> Handle(CreateProductCategoryCommand request, CancellationToken cancellationToken)
{
var ent = ProductCategory.Create(request.Name, request.Description, request.IsMain);

View File

@ -0,0 +1,14 @@
using FluentValidation;
namespace NetinaShop.Repository.Handlers.ProductCategories.Validators;
public class CreateProductCategoryCommandValidator : AbstractValidator<CreateProductCategoryCommand>
{
public CreateProductCategoryCommandValidator()
{
RuleFor(d=>d.Name)
.NotNull()
.NotEmpty()
.WithMessage("نام دسته بندی مورد نظر را وارد کنید");
}
}

View File

@ -0,0 +1,14 @@
using FluentValidation;
namespace NetinaShop.Repository.Handlers.ProductCategories.Validators;
public class UpdateProductCategoryCommandValidator : AbstractValidator<CreateProductCategoryCommand>
{
public UpdateProductCategoryCommandValidator()
{
RuleFor(d => d.Name)
.NotNull()
.NotEmpty()
.WithMessage("نام دسته بندی مورد نظر را وارد کنید");
}
}

View File

@ -72,6 +72,11 @@ public class GetProductsQueryHandler : IRequestHandler<GetProductsQuery, GetProd
.Select(ProductMapper.ProjectToSDto)
.ToListAsync(cancellationToken);
response.Filters.Price.MaximumValue = await products.MaxAsync(p => p.Cost,cancellationToken);
response.Filters.Price.MinimumValue = await products.MinAsync(p => p.Cost,cancellationToken);
foreach (var productSDto in productSDtos)
{
await _mediator.Send(new CalculateProductDiscountCommand(productSDto), cancellationToken);

View File

@ -0,0 +1,34 @@
using FluentValidation;
namespace NetinaShop.Repository.Handlers.Products.Validators;
public class CreateProductCommandValidator : AbstractValidator<CreateProductCommand>
{
public CreateProductCommandValidator()
{
RuleFor(d => d.PersianName)
.NotNull()
.NotEmpty()
.WithMessage("نام فارسی کالا مورد نظر را وارد کنید");
RuleFor(d => d.EnglishName)
.NotNull()
.NotEmpty()
.WithMessage("نام انگلیسی کالا مورد نظر را وارد کنید");
RuleFor(d => d.Summery)
.NotNull()
.NotEmpty()
.WithMessage("توضیحات کوتاه کالا مورد نظر را وارد کنید");
RuleFor(d => d.CategoryId)
.NotEqual(Guid.Empty)
.NotEmpty()
.WithMessage("دسته بندی کالا مورد نظر را وارد کنید");
RuleFor(d => d.BrandId)
.NotEqual(Guid.Empty)
.NotEmpty()
.WithMessage("برند کالا مورد نظر را وارد کنید");
}
}

View File

@ -0,0 +1,34 @@
using FluentValidation;
namespace NetinaShop.Repository.Handlers.Products.Validators;
public class UpdateProductCommandValidator : AbstractValidator<UpdateProductCommand>
{
public UpdateProductCommandValidator()
{
RuleFor(d => d.PersianName)
.NotNull()
.NotEmpty()
.WithMessage("نام فارسی کالا مورد نظر را وارد کنید");
RuleFor(d => d.EnglishName)
.NotNull()
.NotEmpty()
.WithMessage("نام انگلیسی کالا مورد نظر را وارد کنید");
RuleFor(d => d.Summery)
.NotNull()
.NotEmpty()
.WithMessage("توضیحات کوتاه کالا مورد نظر را وارد کنید");
RuleFor(d => d.CategoryId)
.NotEqual(Guid.Empty)
.NotEmpty()
.WithMessage("دسته بندی کالا مورد نظر را وارد کنید");
RuleFor(d => d.BrandId)
.NotEqual(Guid.Empty)
.NotEmpty()
.WithMessage("برند کالا مورد نظر را وارد کنید");
}
}

View File

@ -0,0 +1,24 @@
using FluentValidation;
namespace NetinaShop.Repository.Handlers.Reviews.Validators;
public class CreateReviewCommandValidator : AbstractValidator<CreateReviewCommand>
{
public CreateReviewCommandValidator()
{
RuleFor(d => d.Title)
.NotNull()
.NotEmpty()
.WithMessage("عنوان نظر مورد نظر را وارد کنید");
RuleFor(d => d.Comment)
.NotNull()
.NotEmpty()
.WithMessage("متن نظر مورد نظر را وارد کنید");
RuleFor(d => d.ProductId)
.NotEqual(Guid.Empty)
.NotEmpty()
.WithMessage("کالا مورد نظر را وارد کنید");
}
}

View File

@ -0,0 +1,24 @@
using FluentValidation;
namespace NetinaShop.Repository.Handlers.Reviews.Validators;
public class UpdateReviewCommandValidator : AbstractValidator<UpdateReviewCommand>
{
public UpdateReviewCommandValidator()
{
RuleFor(d => d.Title)
.NotNull()
.NotEmpty()
.WithMessage("عنوان نظر مورد نظر را وارد کنید");
RuleFor(d => d.Comment)
.NotNull()
.NotEmpty()
.WithMessage("متن نظر مورد نظر را وارد کنید");
RuleFor(d => d.ProductId)
.NotEqual(Guid.Empty)
.NotEmpty()
.WithMessage("کالا مورد نظر را وارد کنید");
}
}

View File

@ -0,0 +1,23 @@
using FluentValidation;
namespace NetinaShop.Repository.Handlers.Warehouses.Validators;
public class CreateShippingCommandValidator : AbstractValidator<CreateShippingCommand>
{
public CreateShippingCommandValidator()
{
RuleFor(d => d.Name)
.NotNull()
.NotEmpty()
.WithMessage("نام روش ارسال مورد نظر را وارد کنید");
RuleFor(d => d.WarehouseName)
.NotNull()
.NotEmpty()
.WithMessage("انبار ارسال مورد نظر را وارد کنید");
RuleFor(d => d.DeliveryCost)
.GreaterThan(0)
.WithMessage("هزینه روش ارسال مورد نظر را وارد کنید");
}
}

View File

@ -0,0 +1,23 @@
using FluentValidation;
namespace NetinaShop.Repository.Handlers.Warehouses.Validators;
public class UpdateShippingCommandValidator : AbstractValidator<UpdateShippingCommand>
{
public UpdateShippingCommandValidator()
{
RuleFor(d => d.Name)
.NotNull()
.NotEmpty()
.WithMessage("نام روش ارسال مورد نظر را وارد کنید");
RuleFor(d => d.WarehouseName)
.NotNull()
.NotEmpty()
.WithMessage("انبار ارسال مورد نظر را وارد کنید");
RuleFor(d => d.DeliveryCost)
.GreaterThan(0)
.WithMessage("هزینه روش ارسال مورد نظر را وارد کنید");
}
}

View File

@ -29,8 +29,6 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Handlers\Brands\" />
<Folder Include="Handlers\Complexes\" />
<Folder Include="Models\" />
<Folder Include="Extensions\" />
<Folder Include="Services\Abstracts\" />