Add validators and services for Product Galleries and Product Tags

- Implemented Create, Delete, Get, and Update validators for Product Galleries.
- Added Create, Delete, Get, and Update validators for Product Tags.
- Created service classes for handling Discount Categories, Discount Orders, Discount Products, Discount Shopping Cart, Product Categories, Product Galleries, and Product Tags.
- Each service class integrates with CQRS for command and query handling.
- Established mapping profiles for Product Galleries.
This commit is contained in:
masoodafar-web
2025-12-04 02:40:49 +03:30
parent 40d54d08fc
commit f0f48118e7
436 changed files with 33159 additions and 2005 deletions
@@ -0,0 +1,10 @@
namespace CMSMicroservice.Application.TagCQ.Commands.AssignTagToProduct;
public record AssignTagToProductCommand : IRequest
{
/// <summary>شناسه محصول</summary>
public long ProductId { get; init; }
/// <summary>شناسه تگ</summary>
public long TagId { get; init; }
}
@@ -0,0 +1,57 @@
using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.TagCQ.Commands.AssignTagToProduct;
public class AssignTagToProductCommandHandler : IRequestHandler<AssignTagToProductCommand, Unit>
{
private readonly IApplicationDbContext _context;
public AssignTagToProductCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<Unit> Handle(AssignTagToProductCommand request, CancellationToken cancellationToken)
{
// بررسی وجود محصول
var product = await _context.Products
.FirstOrDefaultAsync(p => p.Id == request.ProductId, cancellationToken);
if (product == null)
{
throw new NotFoundException(nameof(Product), request.ProductId);
}
// بررسی وجود تگ
var tag = await _context.Tags
.FirstOrDefaultAsync(t => t.Id == request.TagId, cancellationToken);
if (tag == null)
{
throw new NotFoundException(nameof(Tag), request.TagId);
}
// بررسی اینکه قبلاً اختصاص داده نشده باشد
var existingProductTag = await _context.ProductTags
.FirstOrDefaultAsync(pt => pt.ProductId == request.ProductId && pt.TagId == request.TagId, cancellationToken);
if (existingProductTag != null)
{
throw new BadRequestException("این تگ قبلاً به این محصول اختصاص داده شده است");
}
var productTag = new ProductTag
{
ProductId = request.ProductId,
TagId = request.TagId
};
_context.ProductTags.Add(productTag);
await _context.SaveChangesAsync(cancellationToken);
return Unit.Value;
}
}
@@ -0,0 +1,13 @@
namespace CMSMicroservice.Application.TagCQ.Commands.AssignTagToProduct;
public class AssignTagToProductCommandValidator : AbstractValidator<AssignTagToProductCommand>
{
public AssignTagToProductCommandValidator()
{
RuleFor(x => x.ProductId)
.GreaterThan(0).WithMessage("شناسه محصول نامعتبر است");
RuleFor(x => x.TagId)
.GreaterThan(0).WithMessage("شناسه تگ نامعتبر است");
}
}
@@ -0,0 +1,19 @@
namespace CMSMicroservice.Application.TagCQ.Commands.CreateTag;
public record CreateTagCommand : IRequest<long>
{
/// <summary>نام لاتین تگ</summary>
public string Name { get; init; }
/// <summary>عنوان فارسی تگ</summary>
public string Title { get; init; }
/// <summary>توضیحات</summary>
public string? Description { get; init; }
/// <summary>ترتیب نمایش</summary>
public int SortOrder { get; init; }
/// <summary>وضعیت فعال/غیرفعال</summary>
public bool IsActive { get; init; } = true;
}
@@ -0,0 +1,42 @@
using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.TagCQ.Commands.CreateTag;
public class CreateTagCommandHandler : IRequestHandler<CreateTagCommand, long>
{
private readonly IApplicationDbContext _context;
public CreateTagCommandHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<long> Handle(CreateTagCommand request, CancellationToken cancellationToken)
{
// بررسی تکراری نبودن نام
var existingTag = await _context.Tags
.FirstOrDefaultAsync(t => t.Name == request.Name, cancellationToken);
if (existingTag != null)
{
throw new BadRequestException($"تگ با نام '{request.Name}' قبلاً ثبت شده است");
}
var tag = new Tag
{
Name = request.Name,
Title = request.Title,
Description = request.Description,
SortOrder = request.SortOrder,
IsActive = request.IsActive
};
_context.Tags.Add(tag);
await _context.SaveChangesAsync(cancellationToken);
return tag.Id;
}
}
@@ -0,0 +1,23 @@
namespace CMSMicroservice.Application.TagCQ.Commands.CreateTag;
public class CreateTagCommandValidator : AbstractValidator<CreateTagCommand>
{
public CreateTagCommandValidator()
{
RuleFor(x => x.Name)
.NotEmpty().WithMessage("نام تگ الزامی است")
.MaximumLength(100).WithMessage("نام تگ نباید بیشتر از 100 کاراکتر باشد")
.Matches("^[a-zA-Z0-9_-]+$").WithMessage("نام تگ فقط باید شامل حروف انگلیسی، اعداد، خط تیره و زیرخط باشد");
RuleFor(x => x.Title)
.NotEmpty().WithMessage("عنوان تگ الزامی است")
.MaximumLength(200).WithMessage("عنوان تگ نباید بیشتر از 200 کاراکتر باشد");
RuleFor(x => x.Description)
.MaximumLength(500).When(x => !string.IsNullOrEmpty(x.Description))
.WithMessage("توضیحات نباید بیشتر از 500 کاراکتر باشد");
RuleFor(x => x.SortOrder)
.GreaterThanOrEqualTo(0).WithMessage("ترتیب نمایش نمی‌تواند منفی باشد");
}
}
@@ -0,0 +1,16 @@
using CMSMicroservice.Application.Common.Models;
using MediatR;
namespace CMSMicroservice.Application.TagCQ.Queries.GetAllTags;
/// <summary>
/// کوئری دریافت همه تگ‌ها با فیلتر و صفحه‌بندی
/// </summary>
public class GetAllTagsQuery : IRequest<GetAllTagsResponseDto>
{
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 20;
public bool? IsActive { get; set; }
public string? SearchTerm { get; set; }
public bool OrderByDescending { get; set; } = false;
}
@@ -0,0 +1,82 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Models;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace CMSMicroservice.Application.TagCQ.Queries.GetAllTags;
public class GetAllTagsQueryHandler : IRequestHandler<GetAllTagsQuery, GetAllTagsResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly ILogger<GetAllTagsQueryHandler> _logger;
public GetAllTagsQueryHandler(
IApplicationDbContext context,
ILogger<GetAllTagsQueryHandler> logger)
{
_context = context;
_logger = logger;
}
public async Task<GetAllTagsResponseDto> Handle(GetAllTagsQuery request, CancellationToken cancellationToken)
{
var query = _context.Tags
.Where(x => !x.IsDeleted);
// فیلترها
if (request.IsActive.HasValue)
{
query = query.Where(x => x.IsActive == request.IsActive.Value);
}
if (!string.IsNullOrEmpty(request.SearchTerm))
{
var searchTerm = request.SearchTerm.ToLower();
query = query.Where(x => x.Name.ToLower().Contains(searchTerm)
|| x.Title.ToLower().Contains(searchTerm));
}
var totalCount = await query.CountAsync(cancellationToken);
// مرتب‌سازی
query = request.OrderByDescending
? query.OrderByDescending(x => x.SortOrder).ThenByDescending(x => x.Created)
: query.OrderBy(x => x.SortOrder).ThenBy(x => x.Created);
// صفحه‌بندی
var tags = await query
.Skip((request.PageNumber - 1) * request.PageSize)
.Take(request.PageSize)
.Select(x => new TagDto
{
Id = x.Id,
Name = x.Name,
Title = x.Title,
Description = x.Description,
IsActive = x.IsActive,
SortOrder = x.SortOrder,
ProductCount = x.ProductTags.Count(p => !p.IsDeleted),
Created = x.Created
})
.ToListAsync(cancellationToken);
var metaData = new MetaData
{
TotalCount = totalCount,
PageSize = request.PageSize,
CurrentPage = request.PageNumber,
TotalPage = (int)Math.Ceiling(totalCount / (double)request.PageSize),
HasNext = request.PageNumber < (int)Math.Ceiling(totalCount / (double)request.PageSize),
HasPrevious = request.PageNumber > 1
};
_logger.LogInformation("Retrieved {Count} tags. Total: {Total}", tags.Count, totalCount);
return new GetAllTagsResponseDto
{
MetaData = metaData,
Tags = tags
};
}
}
@@ -0,0 +1,21 @@
using CMSMicroservice.Application.Common.Models;
namespace CMSMicroservice.Application.TagCQ.Queries.GetAllTags;
public class GetAllTagsResponseDto
{
public MetaData MetaData { get; set; } = new();
public List<TagDto> Tags { get; set; } = new();
}
public class TagDto
{
public long Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
public string? Description { get; set; }
public bool IsActive { get; set; }
public int SortOrder { get; set; }
public int ProductCount { get; set; }
public DateTime Created { get; set; }
}
@@ -0,0 +1,17 @@
namespace CMSMicroservice.Application.TagCQ.Queries.GetProductsByTag;
public record GetProductsByTagQuery : IRequest<List<ProductSimpleDto>>
{
/// <summary>شناسه تگ</summary>
public long TagId { get; init; }
}
public class ProductSimpleDto
{
public long Id { get; set; }
public string Title { get; set; }
public long Price { get; set; }
public int Inventory { get; set; }
public bool IsActive { get; set; }
public string? ImagePath { get; set; }
}
@@ -0,0 +1,44 @@
using CMSMicroservice.Application.Common.Exceptions;
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities;
using Microsoft.EntityFrameworkCore;
namespace CMSMicroservice.Application.TagCQ.Queries.GetProductsByTag;
public class GetProductsByTagQueryHandler : IRequestHandler<GetProductsByTagQuery, List<ProductSimpleDto>>
{
private readonly IApplicationDbContext _context;
public GetProductsByTagQueryHandler(IApplicationDbContext context)
{
_context = context;
}
public async Task<List<ProductSimpleDto>> Handle(GetProductsByTagQuery request, CancellationToken cancellationToken)
{
// بررسی وجود تگ
var tagExists = await _context.Tags
.AnyAsync(t => t.Id == request.TagId, cancellationToken);
if (!tagExists)
{
throw new NotFoundException(nameof(Tag), request.TagId);
}
var products = await _context.ProductTags
.Where(pt => pt.TagId == request.TagId)
.Include(pt => pt.Product)
.Select(pt => new ProductSimpleDto
{
Id = pt.Product.Id,
Title = pt.Product.Title,
Price = pt.Product.Price,
Inventory = pt.Product.RemainingCount,
IsActive = true, // Product entity doesn't have IsActive field
ImagePath = pt.Product.ImagePath
})
.ToListAsync(cancellationToken);
return products;
}
}