From 19f1f29548fec7f8217168f11831da405d9ca7ac Mon Sep 17 00:00:00 2001 From: "Amir.H Khademi" Date: Wed, 7 Aug 2024 16:15:53 +0330 Subject: [PATCH] feat(FaqInActions) , feat(FaqManagementPage) , feat(FaqController) --- Netina.Api/Controllers/FaqController.cs | 56 ++++++++++ Netina.Api/Controllers/SeedController.cs | 18 ++-- .../MiddleWares/ExceptionHandlerMiddleware.cs | 4 +- .../CommandQueries/Commands/BrandCommands.cs | 19 +++- .../CommandQueries/Commands/FaqCommands.cs | 15 +++ .../Commands/ProductCategoryCommands.cs | 8 +- .../Commands/ProductCommands.cs | 47 ++++---- .../CommandQueries/Queries/FaqQueries.cs | 4 + Netina.Domain/Dtos/LargDtos/BrandLDto.cs | 1 + .../Dtos/LargDtos/ProductCategoryLDto.cs | 1 + Netina.Domain/Dtos/SmallDtos/BrandSDto.cs | 1 + Netina.Domain/Entities/Brands/Brand.cs | 5 +- Netina.Domain/Entities/Products/Product.cs | 3 + Netina.Domain/Extensions/BrandExtension.cs | 13 +++ .../Extensions/ProductCategoryExtension.cs | 13 +++ Netina.Domain/Extensions/ProductExtension.cs | 13 +++ Netina.Domain/MartenEntities/Faqs/BaseFaq.cs | 8 ++ Netina.Domain/MartenEntities/Pages/FAQPage.cs | 7 -- .../Models/Claims/ApplicationClaims.cs | 13 ++- .../Models/Claims/ApplicationPermission.cs | 2 + Netina.Domain/Netina.Domain.csproj | 3 + .../Marten/MartenRepository.cs | 57 +++++++--- .../Marten/MartenRepositoryWrapper.cs | 12 +-- .../Services/Scrapers/DigikalaScraper.cs | 2 +- .../Brands/CreateBrandCommandHandler.cs | 32 ++++-- .../Brands/UpdateBrandCommandHandler.cs | 41 ++++--- .../Handlers/Faqs/CreateFaqCommandHandler.cs | 18 ++++ .../Handlers/Faqs/DeleteFaqCommandHandler.cs | 12 +++ .../Handlers/Faqs/GetFaqQueryHandler.cs | 27 +++++ .../Handlers/Faqs/GetFaqsQueryHandler.cs | 16 +++ .../Handlers/Faqs/UpdateFaqCommandHandler.cs | 24 +++++ .../CreateProductCategoryCommandHandler.cs | 31 ++++-- .../DeleteProductCategoryCommandHandler.cs | 19 ++-- .../GetProductCategoriesQueryHandler.cs | 13 +-- .../GetProductCategoryChildrenQueryHandler.cs | 12 +-- .../GetProductCategoryQueryHandler.cs | 11 +- .../UpdateProductCategoryCommandHandler.cs | 42 +++++--- .../Products/CreateProductCommandHandler.cs | 39 ++++--- .../Products/DeleteProductCommandHandler.cs | 15 +-- .../Products/GetProductQueryHandler.cs | 6 +- .../Products/GetProductsQueryHandler.cs | 28 ++--- .../Products/UpdateProductCommandHandler.cs | 33 +++++- Netina.Repository/Netina.Repository.csproj | 4 + .../Repositories/Marten/IMartenRepository.cs | 3 + .../Services/DbInitializerService.cs | 101 +++++++----------- 45 files changed, 588 insertions(+), 264 deletions(-) create mode 100644 Netina.Api/Controllers/FaqController.cs create mode 100644 Netina.Domain/CommandQueries/Commands/FaqCommands.cs create mode 100644 Netina.Domain/CommandQueries/Queries/FaqQueries.cs create mode 100644 Netina.Domain/Extensions/BrandExtension.cs create mode 100644 Netina.Domain/Extensions/ProductCategoryExtension.cs create mode 100644 Netina.Domain/Extensions/ProductExtension.cs create mode 100644 Netina.Domain/MartenEntities/Faqs/BaseFaq.cs delete mode 100644 Netina.Domain/MartenEntities/Pages/FAQPage.cs create mode 100644 Netina.Repository/Handlers/Faqs/CreateFaqCommandHandler.cs create mode 100644 Netina.Repository/Handlers/Faqs/DeleteFaqCommandHandler.cs create mode 100644 Netina.Repository/Handlers/Faqs/GetFaqQueryHandler.cs create mode 100644 Netina.Repository/Handlers/Faqs/GetFaqsQueryHandler.cs create mode 100644 Netina.Repository/Handlers/Faqs/UpdateFaqCommandHandler.cs diff --git a/Netina.Api/Controllers/FaqController.cs b/Netina.Api/Controllers/FaqController.cs new file mode 100644 index 0000000..9b141fe --- /dev/null +++ b/Netina.Api/Controllers/FaqController.cs @@ -0,0 +1,56 @@ +namespace Netina.Api.Controllers; + +public class FaqController : ICarterModule +{ + public void AddRoutes(IEndpointRouteBuilder app) + { + var group = app.NewVersionedApi("Faq") + .MapGroup("api/faq"); + + group.MapGet("/slug", GetFaqBySlugAsync) + .WithDisplayName("GetFaqBySlug") + .WithDescription("Get faq by slug , you have to send page slug") + .HasApiVersion(1.0); + + group.MapGet("", GetFaqsAsync) + .WithDisplayName("GetFaqs") + .WithDescription("Get All Faqs ") + .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer") + .RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ManageFaq)) + .HasApiVersion(1.0); + + group.MapPost("", CreateFaqAsync) + .WithDisplayName("Create Faq") + .WithDescription("Create Faq , you can create new faq or create update your faq ") + .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer") + .RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ManageFaq)) + .HasApiVersion(1.0); + + group.MapPut("", UpdateFaqAsync) + .WithDisplayName("Update FaqAsync") + .WithDescription("Update Faq , you can create new faq or create update your faq ") + .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer") + .RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ManageFaq)) + .HasApiVersion(1.0); + + group.MapDelete("{id}", DeleteFaqAsync) + .WithDisplayName("DeleteFaq") + .RequireAuthorization(builder => builder.AddAuthenticationSchemes("Bearer").RequireAuthenticatedUser().RequireClaim(CustomClaimType.Permission, ApplicationPermission.ManageFaq)) + .HasApiVersion(1.0); + } + private async Task DeleteFaqAsync([FromRoute]Guid id,[FromServices] IMediator mediator, CancellationToken cancellationToken) + => TypedResults.Ok(await mediator.Send(new DeleteFaqCommand(Id:id), cancellationToken)); + + private async Task GetFaqsAsync([FromQuery]int page, [FromQuery] int? count ,[FromServices] IMediator mediator, CancellationToken cancellationToken) + => TypedResults.Ok(await mediator.Send(new GetFaqsQuery(Count: count ?? 0 , Page:page), cancellationToken)); + + private async Task CreateFaqAsync([FromBody] CreateFaqCommand request,[FromServices]IMediator mediator , CancellationToken cancellationToken) + => TypedResults.Ok(await mediator.Send(request, cancellationToken)); + + private async Task UpdateFaqAsync([FromBody] UpdateFaqCommand request, [FromServices] IMediator mediator, CancellationToken cancellationToken) + => TypedResults.Ok(await mediator.Send(request, cancellationToken)); + + private async Task GetFaqBySlugAsync([FromQuery] string slug, [FromServices] IMediator mediator, CancellationToken cancellationToken) + => TypedResults.Ok(await mediator.Send(new GetFaqQuery(null, slug), cancellationToken)); + +} \ No newline at end of file diff --git a/Netina.Api/Controllers/SeedController.cs b/Netina.Api/Controllers/SeedController.cs index acc7f01..567361d 100644 --- a/Netina.Api/Controllers/SeedController.cs +++ b/Netina.Api/Controllers/SeedController.cs @@ -78,13 +78,14 @@ public class SeedController : ICarterModule var baseCat = await mediator.Send(new CreateProductCategoryCommand("دسته بندی نشده", "محصولات دسته بندی نشده", true, default, - new List()),cancellationToken); + new List(), + new Dictionary(), + new Dictionary()),cancellationToken); categories.Add(0,baseCat); foreach (var requestDto in request) { var lDto = await mediator.Send(new CreateProductCategoryCommand(requestDto.Name,requestDto.Description,true,default, - - new List()), cancellationToken); + new List(),new Dictionary(),new Dictionary()), cancellationToken); categories.Add(requestDto.BaseCategoryId,lDto); } @@ -97,13 +98,18 @@ public class SeedController : ICarterModule if (key != "kKAYskyG8xPxKnJrHkuYxub4Ao2bnz7AOmNtwDT0RaqzaG7ZvbvaP29tCrC8wJ823RczJFXOIQT2bDOec4F38A==") throw new AppException("Key is not valid", ApiResultStatusCode.UnAuthorized); Dictionary brands = new Dictionary(); - var baseBrand = await mediator.Send(new CreateBrandCommand("بدون برند","NoBrand", "محصولات بدون برند", false,string.Empty, - new List()), cancellationToken); + var baseBrand = await mediator.Send(new CreateBrandCommand("بدون برند","NoBrand", + "محصولات بدون برند", + false, + string.Empty, + new List(), + new Dictionary(), + new Dictionary()), cancellationToken); brands.Add(0, baseBrand); foreach (var requestDto in request) { var sDto = await mediator.Send(new CreateBrandCommand(requestDto.Name,string.Empty, requestDto.Description, false, - string.Empty, new List()), cancellationToken); + string.Empty, new List(),new Dictionary(),new Dictionary()), cancellationToken); brands.Add(requestDto.BaseBrandId,sDto); } diff --git a/Netina.Api/WebFramework/MiddleWares/ExceptionHandlerMiddleware.cs b/Netina.Api/WebFramework/MiddleWares/ExceptionHandlerMiddleware.cs index 122c097..0f52832 100644 --- a/Netina.Api/WebFramework/MiddleWares/ExceptionHandlerMiddleware.cs +++ b/Netina.Api/WebFramework/MiddleWares/ExceptionHandlerMiddleware.cs @@ -39,7 +39,9 @@ public class ExceptionHandlerMiddleware catch (BaseApiException exception) { _logger.LogError(exception, exception.Message); - httpStatusCode = exception.HttpStatusCode; + httpStatusCode = exception.ApiStatusCode == ApiResultStatusCode.NotFound ? HttpStatusCode.NotFound : + exception.ApiStatusCode == ApiResultStatusCode.BadRequest ? + HttpStatusCode.BadRequest : exception.HttpStatusCode; apiStatusCode = exception.ApiStatusCode; if (_env.IsDevelopment()) diff --git a/Netina.Domain/CommandQueries/Commands/BrandCommands.cs b/Netina.Domain/CommandQueries/Commands/BrandCommands.cs index f9d3281..f8c4d42 100644 --- a/Netina.Domain/CommandQueries/Commands/BrandCommands.cs +++ b/Netina.Domain/CommandQueries/Commands/BrandCommands.cs @@ -1,8 +1,23 @@ namespace Netina.Domain.CommandQueries.Commands; -public sealed record CreateBrandCommand(string PersianName,string EnglishName, string Description , bool HasSpecialPage , string PageUrl, List Files) : IRequest; +public sealed record CreateBrandCommand(string PersianName, + string EnglishName, + string Description , + bool HasSpecialPage , + string PageUrl, + List Files, + Dictionary Faqs, + Dictionary MetaTags) : IRequest; -public sealed record UpdateBrandCommand(Guid Id,string PersianName, string EnglishName, string Description, bool HasSpecialPage, string PageUrl, List Files) : IRequest; +public sealed record UpdateBrandCommand(Guid Id, + string PersianName, + string EnglishName, + string Description, + bool HasSpecialPage, + string PageUrl, + List Files, + Dictionary Faqs, + Dictionary MetaTags) : IRequest; public sealed record DeleteBrandCommand(Guid Id) : IRequest; diff --git a/Netina.Domain/CommandQueries/Commands/FaqCommands.cs b/Netina.Domain/CommandQueries/Commands/FaqCommands.cs new file mode 100644 index 0000000..dac78a8 --- /dev/null +++ b/Netina.Domain/CommandQueries/Commands/FaqCommands.cs @@ -0,0 +1,15 @@ +namespace Netina.Domain.CommandQueries.Commands; + +public record CreateFaqCommand( + Dictionary Faqs, + string Slug, + string Title) : IRequest; + +public record DeleteFaqCommand(Guid Id) : IRequest; + + +public record UpdateFaqCommand( + Guid Id, + Dictionary Faqs, + string Slug, + string Title) : IRequest; \ No newline at end of file diff --git a/Netina.Domain/CommandQueries/Commands/ProductCategoryCommands.cs b/Netina.Domain/CommandQueries/Commands/ProductCategoryCommands.cs index ac59a28..3cc8354 100644 --- a/Netina.Domain/CommandQueries/Commands/ProductCategoryCommands.cs +++ b/Netina.Domain/CommandQueries/Commands/ProductCategoryCommands.cs @@ -5,7 +5,9 @@ public sealed record CreateProductCategoryCommand( string Description, bool IsMain, Guid ParentId, - List Files) : IRequest; + List Files, + Dictionary Faqs, + Dictionary MetaTags) : IRequest; public sealed record UpdateProductCategoryCommand( Guid Id, @@ -13,6 +15,8 @@ public sealed record UpdateProductCategoryCommand( string Description, bool IsMain, Guid ParentId, - List Files) : IRequest; + List Files, + Dictionary Faqs, + Dictionary MetaTags) : IRequest; public sealed record DeleteProductCategoryCommand(Guid Id) : IRequest; \ No newline at end of file diff --git a/Netina.Domain/CommandQueries/Commands/ProductCommands.cs b/Netina.Domain/CommandQueries/Commands/ProductCommands.cs index b15f7a4..17322fe 100644 --- a/Netina.Domain/CommandQueries/Commands/ProductCommands.cs +++ b/Netina.Domain/CommandQueries/Commands/ProductCommands.cs @@ -1,24 +1,26 @@ namespace Netina.Domain.CommandQueries.Commands; public sealed record CreateProductCommand( -string PersianName, -string EnglishName, -string Summery, -string ExpertCheck, -string Tags, -string Warranty, -bool BeDisplayed, -double Cost, -double PackingCost, -int Stock, -bool HasExpressDelivery, -int MaxOrderCount, -bool IsSpecialOffer, -Guid BrandId, -Guid CategoryId, -DiscountSDto SpecialOffer, -List Specifications, -List Files):IRequest; + string PersianName, + string EnglishName, + string Summery, + string ExpertCheck, + string Tags, + string Warranty, + bool BeDisplayed, + double Cost, + double PackingCost, + int Stock, + bool HasExpressDelivery, + int MaxOrderCount, + bool IsSpecialOffer, + Guid BrandId, + Guid CategoryId, + DiscountSDto SpecialOffer, + List Specifications, + List Files, + Dictionary Faqs, + Dictionary MetaTags) : IRequest; public sealed record UpdateProductCommand( Guid Id, @@ -39,10 +41,13 @@ public sealed record UpdateProductCommand( Guid CategoryId, DiscountSDto SpecialOffer, List Specifications, - List Files) : IRequest; + List Files, + Dictionary Faqs, + Dictionary MetaTags) : IRequest; -public sealed record ChangeProductDisplayedCommand(Guid Id,bool BeDisplayed) : IRequest; -public sealed record ChangeProductCostCommand(Guid Id,double Cost) : IRequest; +public sealed record ChangeProductDisplayedCommand(Guid Id, bool BeDisplayed) : IRequest; + +public sealed record ChangeProductCostCommand(Guid Id, double Cost) : IRequest; public sealed record DeleteProductCommand(Guid Id) : IRequest; diff --git a/Netina.Domain/CommandQueries/Queries/FaqQueries.cs b/Netina.Domain/CommandQueries/Queries/FaqQueries.cs new file mode 100644 index 0000000..3fe346a --- /dev/null +++ b/Netina.Domain/CommandQueries/Queries/FaqQueries.cs @@ -0,0 +1,4 @@ +namespace Netina.Domain.CommandQueries.Queries; + +public record GetFaqQuery(Guid? Id,string? Slug) : IRequest; +public record GetFaqsQuery(int Page , int Count = 0) : IRequest>; \ No newline at end of file diff --git a/Netina.Domain/Dtos/LargDtos/BrandLDto.cs b/Netina.Domain/Dtos/LargDtos/BrandLDto.cs index 09101d3..53b5fdd 100644 --- a/Netina.Domain/Dtos/LargDtos/BrandLDto.cs +++ b/Netina.Domain/Dtos/LargDtos/BrandLDto.cs @@ -6,6 +6,7 @@ public class BrandLDto : BaseDto public string EnglishName { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; public bool HasSpecialPage { get; set; } + public string Slug { get; set; } = string.Empty; public string PageUrl { get; set; } = string.Empty; public string HeaderFileName { get; set; } = string.Empty; public List Files { get; internal set; } = new(); diff --git a/Netina.Domain/Dtos/LargDtos/ProductCategoryLDto.cs b/Netina.Domain/Dtos/LargDtos/ProductCategoryLDto.cs index e2a1189..aa4ba9a 100644 --- a/Netina.Domain/Dtos/LargDtos/ProductCategoryLDto.cs +++ b/Netina.Domain/Dtos/LargDtos/ProductCategoryLDto.cs @@ -6,6 +6,7 @@ public class ProductCategoryLDto : BaseDto public string Description { get; set; } = string.Empty; public Guid ParentId { get; set; } public string ParentName { get; set; } = string.Empty; + public string Slug { get; set; } = string.Empty; public bool IsMain { get; set; } public List Children { get; set; } = new(); public List Files { get; internal set; } = new(); diff --git a/Netina.Domain/Dtos/SmallDtos/BrandSDto.cs b/Netina.Domain/Dtos/SmallDtos/BrandSDto.cs index b6db768..acb70df 100644 --- a/Netina.Domain/Dtos/SmallDtos/BrandSDto.cs +++ b/Netina.Domain/Dtos/SmallDtos/BrandSDto.cs @@ -5,6 +5,7 @@ public class BrandSDto : BaseDto public string PersianName { get; set; } = string.Empty; public string EnglishName { get; set; } = string.Empty; public string Description { get; set; } = string.Empty; + public string Slug { get; set; } = string.Empty; public bool HasSpecialPage { get; set; } public string PageUrl { get; set; } = string.Empty; public string HeaderFileName { get; set; } = string.Empty; diff --git a/Netina.Domain/Entities/Brands/Brand.cs b/Netina.Domain/Entities/Brands/Brand.cs index 83907fc..b8eaf1b 100644 --- a/Netina.Domain/Entities/Brands/Brand.cs +++ b/Netina.Domain/Entities/Brands/Brand.cs @@ -1,10 +1,13 @@ -namespace Netina.Domain.Entities.Brands; +using Microsoft.EntityFrameworkCore; + +namespace Netina.Domain.Entities.Brands; [AdaptTwoWays("[name]LDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Map | MapType.MapToTarget | MapType.Projection)] [AdaptTwoWays("[name]SDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Map | MapType.MapToTarget)] [AdaptTo("[name]SDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Projection)] [GenerateMapper] +[Index(nameof(Slug), IsUnique = true)] public partial class Brand : ApiEntity { public Brand() diff --git a/Netina.Domain/Entities/Products/Product.cs b/Netina.Domain/Entities/Products/Product.cs index 94aecd4..6d7ed76 100644 --- a/Netina.Domain/Entities/Products/Product.cs +++ b/Netina.Domain/Entities/Products/Product.cs @@ -6,6 +6,9 @@ [AdaptTo("TorobProductResponseDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Projection)] //[AdaptTo("[name]SDto", IgnoreAttributes = new[] { typeof(AdaptIgnoreAttribute) }, MapType = MapType.Projection)] [GenerateMapper] + + +[Index(nameof(Slug), IsUnique = true)] public partial class Product : ApiEntity { public Product() diff --git a/Netina.Domain/Extensions/BrandExtension.cs b/Netina.Domain/Extensions/BrandExtension.cs new file mode 100644 index 0000000..fe5b826 --- /dev/null +++ b/Netina.Domain/Extensions/BrandExtension.cs @@ -0,0 +1,13 @@ +namespace Netina.Domain.Extensions; + +public static class BrandExtension +{ + public static string GetWebSiteUrl(this Brand product) + => $"/brands/{product.Id}/{product.Slug}"; + + public static string GetWebSiteUrl(this BrandSDto product) + => $"/brands/{product.Id}/{product.Slug}"; + + public static string GetWebSiteUrl(this BrandLDto product) + => $"/brands/{product.Id}/{product.Slug}"; +} \ No newline at end of file diff --git a/Netina.Domain/Extensions/ProductCategoryExtension.cs b/Netina.Domain/Extensions/ProductCategoryExtension.cs new file mode 100644 index 0000000..eed1e8c --- /dev/null +++ b/Netina.Domain/Extensions/ProductCategoryExtension.cs @@ -0,0 +1,13 @@ +namespace Netina.Domain.Extensions; + +public static class ProductCategoryExtension +{ + public static string GetWebSiteUrl(this ProductCategory product) + => $"/categories/{product.Id}/{product.Slug}"; + + public static string GetWebSiteUrl(this ProductCategorySDto product) + => $"/categories/{product.Id}/{product.Slug}"; + + public static string GetWebSiteUrl(this ProductCategoryLDto product) + => $"/categories/{product.Id}/{product.Slug}"; +} \ No newline at end of file diff --git a/Netina.Domain/Extensions/ProductExtension.cs b/Netina.Domain/Extensions/ProductExtension.cs new file mode 100644 index 0000000..0239c9f --- /dev/null +++ b/Netina.Domain/Extensions/ProductExtension.cs @@ -0,0 +1,13 @@ +namespace Netina.Domain.Extensions; + +public static class ProductExtension +{ + public static string GetWebSiteUrl(this Product product) + => $"/products/{product.Id}/{product.Slug}"; + + public static string GetWebSiteUrl(this ProductSDto product) + => $"/products/{product.Id}/{product.Slug}"; + + public static string GetWebSiteUrl(this ProductLDto product) + => $"/products/{product.Id}/{product.Slug}"; +} \ No newline at end of file diff --git a/Netina.Domain/MartenEntities/Faqs/BaseFaq.cs b/Netina.Domain/MartenEntities/Faqs/BaseFaq.cs new file mode 100644 index 0000000..ba6a1a7 --- /dev/null +++ b/Netina.Domain/MartenEntities/Faqs/BaseFaq.cs @@ -0,0 +1,8 @@ +namespace Netina.Domain.MartenEntities.Faqs; + +public class BaseFaq : MartenEntity +{ + public Dictionary Faqs { get; set; } = new(); + public string Slug { get; set; } = string.Empty; + public string Title { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/Netina.Domain/MartenEntities/Pages/FAQPage.cs b/Netina.Domain/MartenEntities/Pages/FAQPage.cs deleted file mode 100644 index 5729a1e..0000000 --- a/Netina.Domain/MartenEntities/Pages/FAQPage.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Netina.Domain.MartenEntities.Pages; - -[PageClassDisplay("FAQPage", "صفحه سوالات متداول")] -public class FAQPage -{ - public Dictionary Faqs { get; set; } = new Dictionary(); -} \ No newline at end of file diff --git a/Netina.Domain/Models/Claims/ApplicationClaims.cs b/Netina.Domain/Models/Claims/ApplicationClaims.cs index aa96581..74842ba 100644 --- a/Netina.Domain/Models/Claims/ApplicationClaims.cs +++ b/Netina.Domain/Models/Claims/ApplicationClaims.cs @@ -11,6 +11,13 @@ public static class ApplicationClaims Value = ApplicationPermission.ManageDashboard, }; + public static ClaimDto ManageFaq { get; } = new ClaimDto + { + Title = "مدیریت سوالات متداول", + Type = CustomClaimType.Permission, + Value = ApplicationPermission.ManageFaq, + }; + public static ClaimDto ManageBlogs { get; } = new ClaimDto { Title = "مدیریت بلاگ ها", @@ -278,7 +285,8 @@ public static class ApplicationClaims ManageUsers, ViewUsers, ManageFiles, - ViewFiles + ViewFiles, + ManageFaq }; public static List AllClaims = new List @@ -319,7 +327,8 @@ public static class ApplicationClaims ManageUsers.GetClaim, ViewUsers.GetClaim, ManageFiles.GetClaim, - ViewFiles.GetClaim + ViewFiles.GetClaim, + ManageFaq.GetClaim }; public static List CustomerClaims = new List diff --git a/Netina.Domain/Models/Claims/ApplicationPermission.cs b/Netina.Domain/Models/Claims/ApplicationPermission.cs index 1876422..0fc7b53 100644 --- a/Netina.Domain/Models/Claims/ApplicationPermission.cs +++ b/Netina.Domain/Models/Claims/ApplicationPermission.cs @@ -2,6 +2,8 @@ public static class ApplicationPermission { + public static string ManageFaq = nameof(ManageFaq); + public const string ViewSettings = nameof(ViewSettings); public const string ManageSettings = nameof(ManageSettings); diff --git a/Netina.Domain/Netina.Domain.csproj b/Netina.Domain/Netina.Domain.csproj index 892e6e1..7003ad6 100644 --- a/Netina.Domain/Netina.Domain.csproj +++ b/Netina.Domain/Netina.Domain.csproj @@ -12,6 +12,7 @@ + @@ -59,6 +60,7 @@ + @@ -78,6 +80,7 @@ + diff --git a/Netina.Infrastructure/Marten/MartenRepository.cs b/Netina.Infrastructure/Marten/MartenRepository.cs index f894b9d..d970213 100644 --- a/Netina.Infrastructure/Marten/MartenRepository.cs +++ b/Netina.Infrastructure/Marten/MartenRepository.cs @@ -1,33 +1,52 @@ using Marten; +using Netina.Repository.Abstracts; namespace Netina.Infrastructure.Marten; -public class MartenRepository : IMartenRepository where TMartenEntity : IMartenEntity +public class MartenRepository(IDocumentStore documentStore, ICurrentUserService currentUserService) + : IMartenRepository + where TMartenEntity : IMartenEntity { - private readonly IDocumentStore _documentStore; - - public MartenRepository(IDocumentStore documentStore) - { - _documentStore = documentStore; - } + private readonly ICurrentUserService _currentUserService = currentUserService; public async Task> GetEntitiesAsync(CancellationToken cancellation) { - await using var session = _documentStore.QuerySession(); - var entities = await session.Query().ToListAsync(cancellation); + await using var session = documentStore.QuerySession(); + var entities = await session + .Query() + .ToListAsync(cancellation); + return entities.ToList(); + } + public async Task> GetEntitiesAsync(int page, int count, CancellationToken cancellation) + { + await using var session = documentStore.QuerySession(); + var entities = await session + .Query() + .Skip(page * count) + .Take(count) + .ToListAsync(cancellation); return entities.ToList(); } + public async Task> GetEntitiesAsync(Expression> expression, int page, int count, CancellationToken cancellation) + { + await using var session = documentStore.QuerySession(); + var entities = await session.Query().Where(expression) + .Skip(page * count) + .Take(count) + .ToListAsync(cancellation); + return entities.ToList(); + } public async Task> GetEntitiesAsync(Expression> expression, CancellationToken cancellation) { - await using var session = _documentStore.QuerySession(); + await using var session = documentStore.QuerySession(); var entities = await session.Query().Where(expression).ToListAsync(cancellation); return entities.ToList(); } public async Task GetEntityAsync(Guid id, CancellationToken cancellation) { - await using var session = _documentStore.QuerySession(); + await using var session = documentStore.QuerySession(); var setting = await session.LoadAsync(id, cancellation); if (setting == null) throw new AppException($"{nameof(setting)} not found", ApiResultStatusCode.NotFound); @@ -36,7 +55,7 @@ public class MartenRepository : IMartenRepository public async Task GetEntityAsync(Expression> expression, CancellationToken cancellation) { - await using var session = _documentStore.QuerySession(); + await using var session = documentStore.QuerySession(); var entity = await session.Query().FirstOrDefaultAsync(expression, cancellation); return entity; } @@ -46,16 +65,26 @@ public class MartenRepository : IMartenRepository if (entity == null) throw new AppException($"{nameof(entity)} is null", ApiResultStatusCode.BadRequest); - await using var session = _documentStore.LightweightSession(); + await using var session = documentStore.LightweightSession(); session.Store(entity); await session.SaveChangesAsync(cancellation); } + public async Task UpdateEntityAsync(TMartenEntity entity, CancellationToken cancellation = default) + { + if (entity == null) + throw new AppException($"{nameof(entity)} is null", ApiResultStatusCode.BadRequest); + + await using var session = documentStore.LightweightSession(); + session.Update(entity); + await session.SaveChangesAsync(cancellation); + } + public async Task RemoveEntityAsync(TMartenEntity entity, CancellationToken cancellation) { if (entity == null) throw new AppException($"{nameof(entity)} is null", ApiResultStatusCode.BadRequest); - await using var session = _documentStore.LightweightSession(); + await using var session = documentStore.LightweightSession(); session.Delete(entity); await session.SaveChangesAsync(cancellation); } diff --git a/Netina.Infrastructure/Marten/MartenRepositoryWrapper.cs b/Netina.Infrastructure/Marten/MartenRepositoryWrapper.cs index 9d9d7b4..77a9b3f 100644 --- a/Netina.Infrastructure/Marten/MartenRepositoryWrapper.cs +++ b/Netina.Infrastructure/Marten/MartenRepositoryWrapper.cs @@ -1,16 +1,10 @@ using Marten; +using Netina.Repository.Abstracts; namespace Netina.Infrastructure.Marten; -public class MartenRepositoryWrapper : IMartenRepositoryWrapper +public class MartenRepositoryWrapper(IDocumentStore documentStore,ICurrentUserService currentUserService) : IMartenRepositoryWrapper { - private readonly IDocumentStore _documentStore; - - public MartenRepositoryWrapper(IDocumentStore documentStore) - { - _documentStore = documentStore; - } - public IMartenRepository SetRepository() where TMartenEntity : IMartenEntity - => new MartenRepository(_documentStore); + => new MartenRepository(documentStore, currentUserService); } \ No newline at end of file diff --git a/Netina.Infrastructure/Services/Scrapers/DigikalaScraper.cs b/Netina.Infrastructure/Services/Scrapers/DigikalaScraper.cs index 831d2e3..d69c51a 100644 --- a/Netina.Infrastructure/Services/Scrapers/DigikalaScraper.cs +++ b/Netina.Infrastructure/Services/Scrapers/DigikalaScraper.cs @@ -102,7 +102,7 @@ public class DigikalaScraper : IDigikalaScraper newSummery, string.Empty, string.Empty, string.Empty, true, 0, 0, 0, false - , 5, false, nonBrand.Id, nonCat.Id, new DiscountSDto(), specifications, files); + , 5, false, nonBrand.Id, nonCat.Id, new DiscountSDto(), specifications, files,new Dictionary(),new Dictionary()); await _mediator.Send(request, cancellationToken); return true; diff --git a/Netina.Repository/Handlers/Brands/CreateBrandCommandHandler.cs b/Netina.Repository/Handlers/Brands/CreateBrandCommandHandler.cs index ecaa60c..c69e2fe 100644 --- a/Netina.Repository/Handlers/Brands/CreateBrandCommandHandler.cs +++ b/Netina.Repository/Handlers/Brands/CreateBrandCommandHandler.cs @@ -2,14 +2,8 @@ namespace Netina.Repository.Handlers.Brands; -public class CreateBrandCommandHandler : IRequestHandler +public class CreateBrandCommandHandler(IRepositoryWrapper repositoryWrapper,IMartenRepositoryWrapper martenRepositoryWrapper , IMediator mediator) : IRequestHandler { - private readonly IRepositoryWrapper _repositoryWrapper; - - public CreateBrandCommandHandler(IRepositoryWrapper repositoryWrapper) - { - _repositoryWrapper = repositoryWrapper; - } public async Task Handle(CreateBrandCommand request, CancellationToken cancellationToken) { var ent = Brand.Create(request.PersianName,request.EnglishName, request.Description, request.HasSpecialPage, request.PageUrl); @@ -17,8 +11,28 @@ public class CreateBrandCommandHandler : IRequestHandler().Add(ent); - await _repositoryWrapper.SaveChangesAsync(cancellationToken); + repositoryWrapper.SetRepository().Add(ent); + await repositoryWrapper.SaveChangesAsync(cancellationToken); + + await UpdateFaqAsync(ent, request.Faqs, cancellationToken); + return ent.Id; } + + + private async Task UpdateFaqAsync(Brand newEnt, Dictionary faqs, CancellationToken cancellationToken) + { + var url = newEnt.GetWebSiteUrl(); + var oldFaq = await martenRepositoryWrapper.SetRepository() + .GetEntityAsync(f => f.Slug == newEnt.Slug, cancellationToken); + var newFaq = await martenRepositoryWrapper.SetRepository() + .GetEntityAsync(f => f.Slug == url, cancellationToken); + if (oldFaq != null) + await mediator.Send(new DeleteFaqCommand(oldFaq.Id), cancellationToken); + + if (newFaq == null) + await mediator.Send(new CreateFaqCommand(faqs, url, newEnt.EnglishName), cancellationToken); + else + await mediator.Send(new UpdateFaqCommand(newFaq.Id, faqs, url, newEnt.EnglishName), cancellationToken); + } } \ No newline at end of file diff --git a/Netina.Repository/Handlers/Brands/UpdateBrandCommandHandler.cs b/Netina.Repository/Handlers/Brands/UpdateBrandCommandHandler.cs index 8b62882..cd9fb10 100644 --- a/Netina.Repository/Handlers/Brands/UpdateBrandCommandHandler.cs +++ b/Netina.Repository/Handlers/Brands/UpdateBrandCommandHandler.cs @@ -1,19 +1,12 @@ -using Microsoft.EntityFrameworkCore; -using Netina.Domain.Entities.Brands; +using Netina.Domain.Entities.Brands; namespace Netina.Repository.Handlers.Brands; -public class UpdateBrandCommandHandler : IRequestHandler +public class UpdateBrandCommandHandler(IRepositoryWrapper repositoryWrapper,IMartenRepositoryWrapper martenRepositoryWrapper,IMediator mediator) : IRequestHandler { - private readonly IRepositoryWrapper _repositoryWrapper; - - public UpdateBrandCommandHandler(IRepositoryWrapper repositoryWrapper) - { - _repositoryWrapper = repositoryWrapper; - } public async Task Handle(UpdateBrandCommand request, CancellationToken cancellationToken) { - var ent = await _repositoryWrapper.SetRepository().TableNoTracking + var ent = await repositoryWrapper.SetRepository().TableNoTracking .FirstOrDefaultAsync(b => b.Id == request.Id, cancellationToken); if (ent == null) throw new AppException("Brand not found"); @@ -22,14 +15,14 @@ public class UpdateBrandCommandHandler : IRequestHandler().TableNoTracking + var dbFiles = await repositoryWrapper.SetRepository().TableNoTracking .Where(s => s.BrandId == newEnt.Id).ToListAsync(cancellationToken); foreach (var dbFile in dbFiles) { if (request.Files.FirstOrDefault(s => s.Id == dbFile.Id) == null) { - _repositoryWrapper.SetRepository().Delete(dbFile); - await _repositoryWrapper.SaveChangesAsync(cancellationToken); + repositoryWrapper.SetRepository().Delete(dbFile); + await repositoryWrapper.SaveChangesAsync(cancellationToken); } } foreach (var file in request.Files.Where(f => f.Id == default)) @@ -37,8 +30,26 @@ public class UpdateBrandCommandHandler : IRequestHandler().Update(newEnt); - await _repositoryWrapper.SaveChangesAsync(cancellationToken); + repositoryWrapper.SetRepository().Update(newEnt); + await repositoryWrapper.SaveChangesAsync(cancellationToken); + await UpdateFaqAsync(newEnt, request.Faqs, cancellationToken); return true; } + + + private async Task UpdateFaqAsync(Brand newEnt, Dictionary faqs, CancellationToken cancellationToken) + { + var url = newEnt.GetWebSiteUrl(); + var oldFaq = await martenRepositoryWrapper.SetRepository() + .GetEntityAsync(f => f.Slug == newEnt.Slug, cancellationToken); + var newFaq = await martenRepositoryWrapper.SetRepository() + .GetEntityAsync(f => f.Slug == url, cancellationToken); + if (oldFaq != null) + await mediator.Send(new DeleteFaqCommand(oldFaq.Id), cancellationToken); + + if (newFaq == null) + await mediator.Send(new CreateFaqCommand(faqs, url, newEnt.EnglishName), cancellationToken); + else + await mediator.Send(new UpdateFaqCommand(newFaq.Id, faqs, url, newEnt.EnglishName), cancellationToken); + } } \ No newline at end of file diff --git a/Netina.Repository/Handlers/Faqs/CreateFaqCommandHandler.cs b/Netina.Repository/Handlers/Faqs/CreateFaqCommandHandler.cs new file mode 100644 index 0000000..f50ba5d --- /dev/null +++ b/Netina.Repository/Handlers/Faqs/CreateFaqCommandHandler.cs @@ -0,0 +1,18 @@ +namespace Netina.Repository.Handlers.Faqs; + +public class CreateFaqCommandHandler(IMartenRepositoryWrapper martenRepositoryWrapper) + : IRequestHandler +{ + public async Task Handle(CreateFaqCommand request, CancellationToken cancellationToken) + { + var faq = new BaseFaq + { + Faqs = request.Faqs, + Slug = request.Slug, + Title = request.Title + }; + await martenRepositoryWrapper.SetRepository() + .AddOrUpdateEntityAsync(faq, cancellationToken); + return true; + } +} \ No newline at end of file diff --git a/Netina.Repository/Handlers/Faqs/DeleteFaqCommandHandler.cs b/Netina.Repository/Handlers/Faqs/DeleteFaqCommandHandler.cs new file mode 100644 index 0000000..bdc92c7 --- /dev/null +++ b/Netina.Repository/Handlers/Faqs/DeleteFaqCommandHandler.cs @@ -0,0 +1,12 @@ +namespace Netina.Repository.Handlers.Faqs; + +public class DeleteFaqCommandHandler(IMartenRepositoryWrapper martenRepositoryWrapper) + : IRequestHandler +{ + public async Task Handle(DeleteFaqCommand request, CancellationToken cancellationToken) + { + var ent = await martenRepositoryWrapper.SetRepository().GetEntityAsync(request.Id, cancellationToken); + await martenRepositoryWrapper.SetRepository().RemoveEntityAsync(ent, cancellationToken); + return true; + } +} \ No newline at end of file diff --git a/Netina.Repository/Handlers/Faqs/GetFaqQueryHandler.cs b/Netina.Repository/Handlers/Faqs/GetFaqQueryHandler.cs new file mode 100644 index 0000000..b030fc5 --- /dev/null +++ b/Netina.Repository/Handlers/Faqs/GetFaqQueryHandler.cs @@ -0,0 +1,27 @@ +namespace Netina.Repository.Handlers.Faqs; + +public class GetFaqQueryHandler(IMartenRepositoryWrapper martenRepositoryWrapper) + : IRequestHandler +{ + public async Task Handle(GetFaqQuery request, CancellationToken cancellationToken) + { + if (request.Id is not null) + { + var ent = await martenRepositoryWrapper.SetRepository() + .GetEntityAsync(request.Id.Value, cancellationToken); + if (ent == null) + return new BaseFaq(); + return ent; + }else if (request.Slug != null) + { + var htmlSlug = request.Slug; + var ent = await martenRepositoryWrapper.SetRepository() + .GetEntityAsync(f=>f.Slug == htmlSlug, cancellationToken); + if (ent == null) + return new BaseFaq(); + return ent; + + } + return new BaseFaq(); + } +} \ No newline at end of file diff --git a/Netina.Repository/Handlers/Faqs/GetFaqsQueryHandler.cs b/Netina.Repository/Handlers/Faqs/GetFaqsQueryHandler.cs new file mode 100644 index 0000000..fcb78ee --- /dev/null +++ b/Netina.Repository/Handlers/Faqs/GetFaqsQueryHandler.cs @@ -0,0 +1,16 @@ +namespace Netina.Repository.Handlers.Faqs; + +public class GetFaqsQueryHandler(IMartenRepositoryWrapper martenRepositoryWrapper) + : IRequestHandler> +{ + public async Task> Handle(GetFaqsQuery request, CancellationToken cancellationToken) + { + var count = request.Count > 0 ? request.Count : 20; + if (count > 50) + throw new BaseApiException(ApiResultStatusCode.BadRequest, "Count limit is 50"); + var response = await martenRepositoryWrapper.SetRepository() + .GetEntitiesAsync(request.Page, count, cancellationToken); + response.ForEach(f => { f.Faqs ??= new Dictionary(); }); + return response; + } +} \ No newline at end of file diff --git a/Netina.Repository/Handlers/Faqs/UpdateFaqCommandHandler.cs b/Netina.Repository/Handlers/Faqs/UpdateFaqCommandHandler.cs new file mode 100644 index 0000000..25c9148 --- /dev/null +++ b/Netina.Repository/Handlers/Faqs/UpdateFaqCommandHandler.cs @@ -0,0 +1,24 @@ +namespace Netina.Repository.Handlers.Faqs; + +public class UpdateFaqCommandHandler(IMartenRepositoryWrapper martenRepositoryWrapper) + : IRequestHandler +{ + public async Task Handle(UpdateFaqCommand request, CancellationToken cancellationToken) + { + var ent = await martenRepositoryWrapper.SetRepository() + .GetEntityAsync(request.Id, cancellationToken); + if (ent == null) + throw new BaseApiException(ApiResultStatusCode.NotFound, "Faq not found"); + + var newEnt = new BaseFaq + { + Id = ent.Id, + Faqs = request.Faqs, + Slug = request.Slug, + Title = request.Title + }; + await martenRepositoryWrapper.SetRepository() + .UpdateEntityAsync(newEnt, cancellationToken); + return true; + } +} \ No newline at end of file diff --git a/Netina.Repository/Handlers/ProductCategories/CreateProductCategoryCommandHandler.cs b/Netina.Repository/Handlers/ProductCategories/CreateProductCategoryCommandHandler.cs index 5d8db21..4ceef7c 100644 --- a/Netina.Repository/Handlers/ProductCategories/CreateProductCategoryCommandHandler.cs +++ b/Netina.Repository/Handlers/ProductCategories/CreateProductCategoryCommandHandler.cs @@ -1,14 +1,8 @@ namespace Netina.Repository.Handlers.ProductCategories; -public class CreateProductCategoryCommandHandler : IRequestHandler +public class CreateProductCategoryCommandHandler(IRepositoryWrapper repositoryWrapper,IMediator mediator,IMartenRepositoryWrapper martenRepositoryWrapper) + : IRequestHandler { - private readonly IRepositoryWrapper _repositoryWrapper; - - public CreateProductCategoryCommandHandler(IRepositoryWrapper repositoryWrapper) - { - _repositoryWrapper = repositoryWrapper; - } - public async Task Handle(CreateProductCategoryCommand request, CancellationToken cancellationToken) { var ent = ProductCategory.Create(request.Name, request.Description, request.IsMain); @@ -18,8 +12,25 @@ public class CreateProductCategoryCommandHandler : IRequestHandler().Add(ent); - await _repositoryWrapper.SaveChangesAsync(cancellationToken); + repositoryWrapper.SetRepository().Add(ent); + await repositoryWrapper.SaveChangesAsync(cancellationToken); + await UpdateFaqAsync(ent, request.Faqs, cancellationToken); return ent.Id; } + + private async Task UpdateFaqAsync(ProductCategory newEnt, Dictionary faqs, CancellationToken cancellationToken) + { + var url = newEnt.GetWebSiteUrl(); + var oldFaq = await martenRepositoryWrapper.SetRepository() + .GetEntityAsync(f => f.Slug == newEnt.Slug, cancellationToken); + var newFaq = await martenRepositoryWrapper.SetRepository() + .GetEntityAsync(f => f.Slug == url, cancellationToken); + if (oldFaq != null) + await mediator.Send(new DeleteFaqCommand(oldFaq.Id), cancellationToken); + + if (newFaq == null) + await mediator.Send(new CreateFaqCommand(faqs, url, newEnt.Name), cancellationToken); + else + await mediator.Send(new UpdateFaqCommand(newFaq.Id, faqs, url, newEnt.Name), cancellationToken); + } } \ No newline at end of file diff --git a/Netina.Repository/Handlers/ProductCategories/DeleteProductCategoryCommandHandler.cs b/Netina.Repository/Handlers/ProductCategories/DeleteProductCategoryCommandHandler.cs index 21238ad..16015a7 100644 --- a/Netina.Repository/Handlers/ProductCategories/DeleteProductCategoryCommandHandler.cs +++ b/Netina.Repository/Handlers/ProductCategories/DeleteProductCategoryCommandHandler.cs @@ -1,24 +1,17 @@ -using Microsoft.EntityFrameworkCore; +namespace Netina.Repository.Handlers.ProductCategories; -namespace Netina.Repository.Handlers.ProductCategories; - -public class DeleteProductCategoryCommandHandler : IRequestHandler +public class DeleteProductCategoryCommandHandler(IRepositoryWrapper repositoryWrapper) + : IRequestHandler { - private readonly IRepositoryWrapper _repositoryWrapper; - - public DeleteProductCategoryCommandHandler(IRepositoryWrapper repositoryWrapper) - { - _repositoryWrapper = repositoryWrapper; - } public async Task Handle(DeleteProductCategoryCommand request, CancellationToken cancellationToken) { - var ent = await _repositoryWrapper.SetRepository().TableNoTracking + var ent = await repositoryWrapper.SetRepository().TableNoTracking .FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken); if (ent == null) throw new AppException("ProductCategory not found", ApiResultStatusCode.NotFound); - _repositoryWrapper.SetRepository().Delete(ent); - await _repositoryWrapper.SaveChangesAsync(cancellationToken); + repositoryWrapper.SetRepository().Delete(ent); + await repositoryWrapper.SaveChangesAsync(cancellationToken); return true; } } \ No newline at end of file diff --git a/Netina.Repository/Handlers/ProductCategories/GetProductCategoriesQueryHandler.cs b/Netina.Repository/Handlers/ProductCategories/GetProductCategoriesQueryHandler.cs index 48e6b73..9cf674f 100644 --- a/Netina.Repository/Handlers/ProductCategories/GetProductCategoriesQueryHandler.cs +++ b/Netina.Repository/Handlers/ProductCategories/GetProductCategoriesQueryHandler.cs @@ -2,21 +2,16 @@ namespace Netina.Repository.Handlers.ProductCategories; -public class GetProductCategoriesQueryHandler : IRequestHandler> +public class GetProductCategoriesQueryHandler(IRepositoryWrapper repositoryWrapper) + : IRequestHandler> { - private readonly IRepositoryWrapper _repositoryWrapper; - - public GetProductCategoriesQueryHandler(IRepositoryWrapper repositoryWrapper) - { - _repositoryWrapper = repositoryWrapper; - } public async Task> Handle(GetProductCategoriesQuery request, CancellationToken cancellationToken) { IQueryable basCategories; List groupCats; if (request.CategoryName != null) { - basCategories = _repositoryWrapper.SetRepository() + basCategories = repositoryWrapper.SetRepository() .TableNoTracking .Where(c => c.Name.Trim().Contains(request.CategoryName.Trim())) .OrderByDescending(c => c.CreatedAt); @@ -24,7 +19,7 @@ public class GetProductCategoriesQueryHandler : IRequestHandler() + basCategories = repositoryWrapper.SetRepository() .TableNoTracking .OrderByDescending(c=>c.CreatedAt); } diff --git a/Netina.Repository/Handlers/ProductCategories/GetProductCategoryChildrenQueryHandler.cs b/Netina.Repository/Handlers/ProductCategories/GetProductCategoryChildrenQueryHandler.cs index 28a847c..783cea4 100644 --- a/Netina.Repository/Handlers/ProductCategories/GetProductCategoryChildrenQueryHandler.cs +++ b/Netina.Repository/Handlers/ProductCategories/GetProductCategoryChildrenQueryHandler.cs @@ -2,15 +2,9 @@ namespace Netina.Repository.Handlers.ProductCategories; -public class GetProductCategoryChildrenQueryHandler : IRequestHandler> +public class GetProductCategoryChildrenQueryHandler(IRepositoryWrapper repositoryWrapper) + : IRequestHandler> { - private readonly IRepositoryWrapper _repositoryWrapper; - - public GetProductCategoryChildrenQueryHandler(IRepositoryWrapper repositoryWrapper) - { - _repositoryWrapper = repositoryWrapper; - } - public async Task> Handle(GetProductCategoryChildrenQuery request, CancellationToken cancellationToken) { if (request.Id == default) @@ -23,7 +17,7 @@ public class GetProductCategoryChildrenQueryHandler : IRequestHandler> Recursive(Guid id,CancellationToken cancellationToken) { - var children = await _repositoryWrapper.SetRepository() + var children = await repositoryWrapper.SetRepository() .TableNoTracking .Where(c => c.ParentId == id) .Select(c => c.Id) diff --git a/Netina.Repository/Handlers/ProductCategories/GetProductCategoryQueryHandler.cs b/Netina.Repository/Handlers/ProductCategories/GetProductCategoryQueryHandler.cs index a13f531..4650a15 100644 --- a/Netina.Repository/Handlers/ProductCategories/GetProductCategoryQueryHandler.cs +++ b/Netina.Repository/Handlers/ProductCategories/GetProductCategoryQueryHandler.cs @@ -2,17 +2,12 @@ namespace Netina.Repository.Handlers.ProductCategories; -public class GetProductCategoryQueryHandler : IRequestHandler +public class GetProductCategoryQueryHandler(IRepositoryWrapper repositoryWrapper) + : IRequestHandler { - private readonly IRepositoryWrapper _repositoryWrapper; - - public GetProductCategoryQueryHandler(IRepositoryWrapper repositoryWrapper) - { - _repositoryWrapper = repositoryWrapper; - } public async Task Handle(GetProductCategoryQuery request, CancellationToken cancellationToken) { - var ent = await _repositoryWrapper.SetRepository().TableNoTracking + var ent = await repositoryWrapper.SetRepository().TableNoTracking .Where(b => b.Id == request.Id) .Select(ProductCategoryMapper.ProjectToLDto) .FirstOrDefaultAsync(cancellationToken); diff --git a/Netina.Repository/Handlers/ProductCategories/UpdateProductCategoryCommandHandler.cs b/Netina.Repository/Handlers/ProductCategories/UpdateProductCategoryCommandHandler.cs index 84e2d76..cb7be47 100644 --- a/Netina.Repository/Handlers/ProductCategories/UpdateProductCategoryCommandHandler.cs +++ b/Netina.Repository/Handlers/ProductCategories/UpdateProductCategoryCommandHandler.cs @@ -1,18 +1,11 @@ -using Microsoft.EntityFrameworkCore; +namespace Netina.Repository.Handlers.ProductCategories; -namespace Netina.Repository.Handlers.ProductCategories; - -public class UpdateProductCategoryCommandHandler : IRequestHandler +public class UpdateProductCategoryCommandHandler(IRepositoryWrapper repositoryWrapper,IMediator mediator,IMartenRepositoryWrapper martenRepositoryWrapper) + : IRequestHandler { - private readonly IRepositoryWrapper _repositoryWrapper; - - public UpdateProductCategoryCommandHandler(IRepositoryWrapper repositoryWrapper) - { - _repositoryWrapper = repositoryWrapper; - } public async Task Handle(UpdateProductCategoryCommand request, CancellationToken cancellationToken) { - var ent = await _repositoryWrapper.SetRepository().TableNoTracking + var ent = await repositoryWrapper.SetRepository().TableNoTracking .FirstOrDefaultAsync(c => c.Id == request.Id, cancellationToken); if (ent == null) throw new AppException("ProductCategory not found", ApiResultStatusCode.NotFound); @@ -25,22 +18,39 @@ public class UpdateProductCategoryCommandHandler : IRequestHandler().TableNoTracking + var dbFiles = await repositoryWrapper.SetRepository().TableNoTracking .Where(s => s.CategoryId == newEnt.Id).ToListAsync(cancellationToken); foreach (var dbFile in dbFiles) { if (request.Files.FirstOrDefault(s => s.Id == dbFile.Id) == null) { - _repositoryWrapper.SetRepository().Delete(dbFile); - await _repositoryWrapper.SaveChangesAsync(cancellationToken); + repositoryWrapper.SetRepository().Delete(dbFile); + await repositoryWrapper.SaveChangesAsync(cancellationToken); } } foreach (var file in request.Files.Where(f => f.Id == default)) { newEnt.AddFile(file.Name, file.FileLocation, file.FileName, file.IsHeader, file.IsPrimary, file.FileType); } - _repositoryWrapper.SetRepository().Update(newEnt); - await _repositoryWrapper.SaveChangesAsync(cancellationToken); + repositoryWrapper.SetRepository().Update(newEnt); + await repositoryWrapper.SaveChangesAsync(cancellationToken); + await UpdateFaqAsync(newEnt, request.Faqs, cancellationToken); return true; } + + private async Task UpdateFaqAsync(ProductCategory newEnt, Dictionary faqs, CancellationToken cancellationToken) + { + var url = newEnt.GetWebSiteUrl(); + var oldFaq = await martenRepositoryWrapper.SetRepository() + .GetEntityAsync(f => f.Slug == newEnt.Slug, cancellationToken); + var newFaq = await martenRepositoryWrapper.SetRepository() + .GetEntityAsync(f => f.Slug == url, cancellationToken); + if (oldFaq != null) + await mediator.Send(new DeleteFaqCommand(oldFaq.Id), cancellationToken); + + if (newFaq == null) + await mediator.Send(new CreateFaqCommand(faqs, url, newEnt.Name), cancellationToken); + else + await mediator.Send(new UpdateFaqCommand(newFaq.Id, faqs, url, newEnt.Name), cancellationToken); + } } \ No newline at end of file diff --git a/Netina.Repository/Handlers/Products/CreateProductCommandHandler.cs b/Netina.Repository/Handlers/Products/CreateProductCommandHandler.cs index 9687a19..6c57899 100644 --- a/Netina.Repository/Handlers/Products/CreateProductCommandHandler.cs +++ b/Netina.Repository/Handlers/Products/CreateProductCommandHandler.cs @@ -1,16 +1,8 @@ namespace Netina.Repository.Handlers.Products; -public class CreateProductCommandHandler : IRequestHandler +public class CreateProductCommandHandler(IRepositoryWrapper repositoryWrapper,IMartenRepositoryWrapper martenRepositoryWrapper, IMediator mediator) + : IRequestHandler { - private readonly IRepositoryWrapper _repositoryWrapper; - private readonly IMediator _mediator; - - public CreateProductCommandHandler(IRepositoryWrapper repositoryWrapper,IMediator mediator) - { - _repositoryWrapper = repositoryWrapper; - _mediator = mediator; - } - public async Task Handle(CreateProductCommand request, CancellationToken cancellationToken) { @@ -34,8 +26,8 @@ public class CreateProductCommandHandler : IRequestHandler().Add(ent); - await _repositoryWrapper.SaveChangesAsync(cancellationToken); + repositoryWrapper.SetRepository().Add(ent); + await repositoryWrapper.SaveChangesAsync(cancellationToken); if (request.IsSpecialOffer) { @@ -48,15 +40,34 @@ public class CreateProductCommandHandler : IRequestHandler(); - await _mediator.Send(discountRequest, cancellationToken); + await mediator.Send(discountRequest, cancellationToken); } else { var discountRequest = discount.Adapt(); - await _mediator.Send(discountRequest, cancellationToken); + await mediator.Send(discountRequest, cancellationToken); } } + await UpdateFaqAsync(ent, request.Faqs, cancellationToken); + return ent.AdaptToLDto(); } + + + private async Task UpdateFaqAsync(Product newEnt, Dictionary faqs, CancellationToken cancellationToken) + { + var url = newEnt.GetWebSiteUrl(); + var oldFaq = await martenRepositoryWrapper.SetRepository() + .GetEntityAsync(f => f.Slug == newEnt.Slug, cancellationToken); + var newFaq = await martenRepositoryWrapper.SetRepository() + .GetEntityAsync(f => f.Slug == url, cancellationToken); + if (oldFaq != null) + await mediator.Send(new DeleteFaqCommand(oldFaq.Id), cancellationToken); + + if (newFaq == null) + await mediator.Send(new CreateFaqCommand(faqs, url, newEnt.EnglishName), cancellationToken); + else + await mediator.Send(new UpdateFaqCommand(newFaq.Id, faqs, url, newEnt.EnglishName), cancellationToken); + } } \ No newline at end of file diff --git a/Netina.Repository/Handlers/Products/DeleteProductCommandHandler.cs b/Netina.Repository/Handlers/Products/DeleteProductCommandHandler.cs index bbb587b..574bf3a 100644 --- a/Netina.Repository/Handlers/Products/DeleteProductCommandHandler.cs +++ b/Netina.Repository/Handlers/Products/DeleteProductCommandHandler.cs @@ -2,23 +2,18 @@ namespace Netina.Repository.Handlers.Products; -public class DeleteProductCommandHandler : IRequestHandler +public class DeleteProductCommandHandler(IRepositoryWrapper repositoryWrapper) + : IRequestHandler { - private readonly IRepositoryWrapper _repositoryWrapper; - - public DeleteProductCommandHandler(IRepositoryWrapper repositoryWrapper) - { - _repositoryWrapper = repositoryWrapper; - } public async Task Handle(DeleteProductCommand request, CancellationToken cancellationToken) { - var ent = await _repositoryWrapper.SetRepository().TableNoTracking + var ent = await repositoryWrapper.SetRepository().TableNoTracking .FirstOrDefaultAsync(d => d.Id == request.Id, cancellationToken); if (ent == null) throw new AppException("Product NotFound", ApiResultStatusCode.NotFound); - _repositoryWrapper.SetRepository().Delete(ent); - await _repositoryWrapper.SaveChangesAsync(cancellationToken); + repositoryWrapper.SetRepository().Delete(ent); + await repositoryWrapper.SaveChangesAsync(cancellationToken); return true; } } \ No newline at end of file diff --git a/Netina.Repository/Handlers/Products/GetProductQueryHandler.cs b/Netina.Repository/Handlers/Products/GetProductQueryHandler.cs index c9c74e6..65b15a3 100644 --- a/Netina.Repository/Handlers/Products/GetProductQueryHandler.cs +++ b/Netina.Repository/Handlers/Products/GetProductQueryHandler.cs @@ -1,6 +1,4 @@ -using Microsoft.EntityFrameworkCore; - -namespace Netina.Repository.Handlers.Products; +namespace Netina.Repository.Handlers.Products; public class GetProductQueryHandler(IRepositoryWrapper repositoryWrapper, IMediator mediator) : IRequestHandler @@ -13,7 +11,7 @@ public class GetProductQueryHandler(IRepositoryWrapper repositoryWrapper, IMedia .FirstOrDefaultAsync(cancellationToken); if (ent == null) - throw new AppException("Product not found", ApiResultStatusCode.NotFound); + throw new BaseApiException(ApiResultStatusCode.NotFound,"Product not found"); await mediator.Send(new CalculateProductDiscountCommand(ent), cancellationToken); diff --git a/Netina.Repository/Handlers/Products/GetProductsQueryHandler.cs b/Netina.Repository/Handlers/Products/GetProductsQueryHandler.cs index 3da07f3..680559e 100644 --- a/Netina.Repository/Handlers/Products/GetProductsQueryHandler.cs +++ b/Netina.Repository/Handlers/Products/GetProductsQueryHandler.cs @@ -2,25 +2,19 @@ namespace Netina.Repository.Handlers.Products; -public class GetProductsQueryHandler : IRequestHandler +public class GetProductsQueryHandler( + IRepositoryWrapper repositoryWrapper, + IMediator mediator, + ICurrentUserService currentUserService) + : IRequestHandler { - private readonly IRepositoryWrapper _repositoryWrapper; - private readonly IMediator _mediator; - private readonly ICurrentUserService _currentUserService; - - public GetProductsQueryHandler(IRepositoryWrapper repositoryWrapper, IMediator mediator, ICurrentUserService currentUserService) - { - _repositoryWrapper = repositoryWrapper; - _mediator = mediator; - _currentUserService = currentUserService; - } public async Task Handle(GetProductsQuery request, CancellationToken cancellationToken) { var response = new GetProductsResponseDto(); - var products = _repositoryWrapper.SetRepository().TableNoTracking; - if (_currentUserService.JwtToken == null) + var products = repositoryWrapper.SetRepository().TableNoTracking; + if (currentUserService.JwtToken == null) products = products.Where(p => p.BeDisplayed); - var roleClaim = _currentUserService.JwtToken?.Claims.FirstOrDefault(c => c.Type == "role"); + var roleClaim = currentUserService.JwtToken?.Claims.FirstOrDefault(c => c.Type == "role"); if (roleClaim != null && roleClaim.Value.Contains("Customer")) products = products.Where(p => p.BeDisplayed); @@ -46,7 +40,7 @@ public class GetProductsQueryHandler : IRequestHandler cats.Contains(p.CategoryId)); } if (request.BrandIds is { Length: > 0 }) @@ -61,7 +55,7 @@ public class GetProductsQueryHandler : IRequestHandler() + var productDiscount = await repositoryWrapper.SetRepository() .TableNoTracking .Where(d => d.HasCode == false && d.IsSpecialOffer && d.ExpireDate.Date >= DateTime.Today.Date) .OrderByDescending(d => d.CreatedAt) @@ -92,7 +86,7 @@ public class GetProductsQueryHandler : IRequestHandler { public async Task Handle(UpdateProductCommand request, CancellationToken cancellationToken) @@ -82,7 +80,34 @@ public class UpdateProductCommandHandler(IRepositoryWrapper repositoryWrapper, I await mediator.Send(discountRequest, cancellationToken); } } + else + { + var discount = await repositoryWrapper.SetRepository() + .TableNoTracking + .FirstOrDefaultAsync(d => d.ProductId == newEnt.Id && d.IsSpecialOffer, cancellationToken); + if (discount != null) + await mediator.Send(new DeleteDiscountCommand(discount.Id), cancellationToken); + } + + await UpdateFaqAsync(newEnt, request.Faqs, cancellationToken); return true; } + + + private async Task UpdateFaqAsync(Product newEnt, Dictionary faqs, CancellationToken cancellationToken) + { + var url = newEnt.GetWebSiteUrl(); + var oldFaq = await martenRepositoryWrapper.SetRepository() + .GetEntityAsync(f => f.Slug == newEnt.Slug, cancellationToken); + var newFaq = await martenRepositoryWrapper.SetRepository() + .GetEntityAsync(f => f.Slug == url, cancellationToken); + if (oldFaq != null) + await mediator.Send(new DeleteFaqCommand(oldFaq.Id), cancellationToken); + + if (newFaq == null) + await mediator.Send(new CreateFaqCommand(faqs, url, newEnt.EnglishName), cancellationToken); + else + await mediator.Send(new UpdateFaqCommand(newFaq.Id, faqs, url, newEnt.EnglishName), cancellationToken); + } } \ No newline at end of file diff --git a/Netina.Repository/Netina.Repository.csproj b/Netina.Repository/Netina.Repository.csproj index 32e41eb..d5f6d82 100644 --- a/Netina.Repository/Netina.Repository.csproj +++ b/Netina.Repository/Netina.Repository.csproj @@ -45,6 +45,7 @@ + @@ -63,7 +64,9 @@ + + @@ -71,6 +74,7 @@ + diff --git a/Netina.Repository/Repositories/Marten/IMartenRepository.cs b/Netina.Repository/Repositories/Marten/IMartenRepository.cs index 441659d..1439050 100644 --- a/Netina.Repository/Repositories/Marten/IMartenRepository.cs +++ b/Netina.Repository/Repositories/Marten/IMartenRepository.cs @@ -6,10 +6,13 @@ public interface IMartenRepository : IScopedDependency where TMar { Task> GetEntitiesAsync(CancellationToken cancellation = default); Task> GetEntitiesAsync(Expression> expression, CancellationToken cancellation = default); + Task> GetEntitiesAsync(int page, int count, CancellationToken cancellation); + Task> GetEntitiesAsync(Expression> expression, int page, int count, CancellationToken cancellation); Task GetEntityAsync(Guid id, CancellationToken cancellation = default); Task GetEntityAsync(Expression> expression, CancellationToken cancellation = default); Task AddOrUpdateEntityAsync(TMartenEntity entity, CancellationToken cancellation = default); + Task UpdateEntityAsync(TMartenEntity entity, CancellationToken cancellation = default); Task RemoveEntityAsync(TMartenEntity entity, CancellationToken cancellation = default); } \ No newline at end of file diff --git a/Netina.Repository/Services/DbInitializerService.cs b/Netina.Repository/Services/DbInitializerService.cs index d8d179d..66c27cb 100644 --- a/Netina.Repository/Services/DbInitializerService.cs +++ b/Netina.Repository/Services/DbInitializerService.cs @@ -1,44 +1,25 @@ -using Microsoft.EntityFrameworkCore; +namespace Netina.Repository.Services; -namespace Netina.Repository.Services; - - -public class DbInitializerService : IDbInitializerService +public class DbInitializerService( + ApplicationContext context, + RoleManager roleManager, + UserManager userManager, + IOptionsSnapshot adminUserSeedOptions, + ILogger logger, + IRepositoryWrapper repositoryWrapper) + : IDbInitializerService { - private readonly IOptionsSnapshot _adminUserSeedOptions; - private readonly ApplicationContext _context; - private readonly ILogger _logger; - private readonly IRepositoryWrapper _repositoryWrapper; - private readonly RoleManager _roleManager; - private readonly UserManager _userManager; - - public DbInitializerService( - ApplicationContext context, - RoleManager roleManager, - UserManager userManager, - IOptionsSnapshot adminUserSeedOptions, - ILogger logger, - IRepositoryWrapper repositoryWrapper) - { - _context = context; - _roleManager = roleManager; - _userManager = userManager; - _adminUserSeedOptions = adminUserSeedOptions; - _logger = logger; - _repositoryWrapper = repositoryWrapper; - } - public void Initialize() { try { - _context.Database.Migrate(); - _context.Database.ExecuteSqlRaw("CREATE EXTENSION IF NOT EXISTS pg_trgm"); - _logger.LogInformation("Migration SUCCESS !!!!"); + context.Database.Migrate(); + context.Database.ExecuteSqlRaw("CREATE EXTENSION IF NOT EXISTS pg_trgm"); + logger.LogInformation("Migration SUCCESS !!!!"); } catch (Exception e) { - _logger.LogError(e, e.Message); + logger.LogError(e, e.Message); } } @@ -47,9 +28,9 @@ public class DbInitializerService : IDbInitializerService try { await SeedRoles(); - var seedAdmin = _adminUserSeedOptions.Value.UserSetting; - var manager = _adminUserSeedOptions.Value.Manager; - var user = await _userManager.FindByNameAsync(seedAdmin.Username); + var seedAdmin = adminUserSeedOptions.Value.UserSetting; + var manager = adminUserSeedOptions.Value.Manager; + var user = await userManager.FindByNameAsync(seedAdmin.Username); if (user == null) { var adminUser = new ApplicationUser @@ -65,17 +46,17 @@ public class DbInitializerService : IDbInitializerService PhoneNumber = seedAdmin.Phone, BirthDate = DateTime.Now.AddYears(-23) }; - var adminUserResult = await _userManager.CreateAsync(adminUser, seedAdmin.Password); - _repositoryWrapper.SetRepository() + var adminUserResult = await userManager.CreateAsync(adminUser, seedAdmin.Password); + repositoryWrapper.SetRepository() .Add(new Manager { UserId = adminUser.Id }); - await _repositoryWrapper.SaveChangesAsync(default); - if (adminUserResult.Succeeded) await _userManager.AddToRoleAsync(adminUser, seedAdmin.RoleName); + await repositoryWrapper.SaveChangesAsync(default); + if (adminUserResult.Succeeded) await userManager.AddToRoleAsync(adminUser, seedAdmin.RoleName); } - var mahanUser = await _userManager.FindByNameAsync(manager.Username); + var mahanUser = await userManager.FindByNameAsync(manager.Username); if (mahanUser == null) { mahanUser = new ApplicationUser @@ -91,14 +72,14 @@ public class DbInitializerService : IDbInitializerService PhoneNumber = manager.Phone, BirthDate = DateTime.Now.AddYears(-23) }; - var adminUserResult = await _userManager.CreateAsync(mahanUser, seedAdmin.Password); - _repositoryWrapper.SetRepository() + var adminUserResult = await userManager.CreateAsync(mahanUser, seedAdmin.Password); + repositoryWrapper.SetRepository() .Add(new Manager { UserId = mahanUser.Id }); - await _repositoryWrapper.SaveChangesAsync(default); - if (adminUserResult.Succeeded) await _userManager.AddToRoleAsync(mahanUser, "Manager"); + await repositoryWrapper.SaveChangesAsync(default); + if (adminUserResult.Succeeded) await userManager.AddToRoleAsync(mahanUser, "Manager"); } } catch (Exception e) @@ -110,8 +91,8 @@ public class DbInitializerService : IDbInitializerService public async Task SeedRoles() { - var seedAdmin = _adminUserSeedOptions.Value.UserSetting; - var rootRole = await _roleManager.FindByNameAsync(seedAdmin.RoleName); + var seedAdmin = adminUserSeedOptions.Value.UserSetting; + var rootRole = await roleManager.FindByNameAsync(seedAdmin.RoleName); if (rootRole == null) { rootRole = new ApplicationRole @@ -120,21 +101,21 @@ public class DbInitializerService : IDbInitializerService EnglishName = seedAdmin.RoleName, Description = "root admin role" }; - var adminRoleResult = await _roleManager.CreateAsync(rootRole); + var adminRoleResult = await roleManager.CreateAsync(rootRole); foreach (var claim in ApplicationClaims.AllClaims) - await _roleManager.AddClaimAsync(rootRole, claim); + await roleManager.AddClaimAsync(rootRole, claim); } else { foreach (var claim in ApplicationClaims.AllClaims) { - var claims = await _roleManager.GetClaimsAsync(rootRole); + var claims = await roleManager.GetClaimsAsync(rootRole); if (claims.FirstOrDefault(c => c.Value == claim.Value) == null) - await _roleManager.AddClaimAsync(rootRole, claim); + await roleManager.AddClaimAsync(rootRole, claim); } } - var managerRole = await _roleManager.FindByNameAsync("Manager"); + var managerRole = await roleManager.FindByNameAsync("Manager"); if (managerRole == null) { managerRole = new ApplicationRole @@ -144,21 +125,21 @@ public class DbInitializerService : IDbInitializerService PersianName = "مدیریتـــ", Description = "admin role" }; - var adminRoleResult = await _roleManager.CreateAsync(managerRole); + var adminRoleResult = await roleManager.CreateAsync(managerRole); foreach (var claim in ApplicationClaims.AllClaims) - await _roleManager.AddClaimAsync(managerRole, claim); + await roleManager.AddClaimAsync(managerRole, claim); } else { foreach (var claim in ApplicationClaims.AllClaims) { - var claims = await _roleManager.GetClaimsAsync(managerRole); + var claims = await roleManager.GetClaimsAsync(managerRole); if (claims.FirstOrDefault(c => c.Value == claim.Value) == null) - await _roleManager.AddClaimAsync(managerRole, claim); + await roleManager.AddClaimAsync(managerRole, claim); } } - var customerRole = await _roleManager.FindByNameAsync("Customer"); + var customerRole = await roleManager.FindByNameAsync("Customer"); if (customerRole == null) { customerRole = new ApplicationRole @@ -168,17 +149,17 @@ public class DbInitializerService : IDbInitializerService EnglishName = "Customer", }; - var customerRoleResult = await _roleManager.CreateAsync(customerRole); + var customerRoleResult = await roleManager.CreateAsync(customerRole); foreach (var claim in ApplicationClaims.CustomerClaims) - await _roleManager.AddClaimAsync(customerRole, claim); + await roleManager.AddClaimAsync(customerRole, claim); } else { foreach (var claim in ApplicationClaims.CustomerClaims) { - var claims = await _roleManager.GetClaimsAsync(customerRole); + var claims = await roleManager.GetClaimsAsync(customerRole); if (claims.FirstOrDefault(c => c.Value == claim.Value) == null) - await _roleManager.AddClaimAsync(customerRole, claim); + await roleManager.AddClaimAsync(customerRole, claim); } } }