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

View File

@@ -0,0 +1,51 @@
using CMSMicroservice.Domain.Enums;
using MediatR;
namespace CMSMicroservice.Application.PublicMessageCQ.Commands.CreatePublicMessage;
/// <summary>
/// دستور ایجاد پیام عمومی
/// Admin می‌تواند پیام عمومی برای نمایش در داشبورد کاربران ایجاد کند
/// </summary>
public class CreatePublicMessageCommand : IRequest<long>
{
/// <summary>
/// عنوان پیام (حداکثر 200 کاراکتر)
/// </summary>
public string Title { get; set; } = string.Empty;
/// <summary>
/// محتوای پیام (حداکثر 2000 کاراکتر)
/// </summary>
public string Content { get; set; } = string.Empty;
/// <summary>
/// نوع پیام
/// </summary>
public MessageType Type { get; set; }
/// <summary>
/// اولویت پیام
/// </summary>
public MessagePriority Priority { get; set; }
/// <summary>
/// تاریخ شروع نمایش
/// </summary>
public DateTime StartsAt { get; set; }
/// <summary>
/// تاریخ پایان نمایش
/// </summary>
public DateTime ExpiresAt { get; set; }
/// <summary>
/// لینک اختیاری
/// </summary>
public string? LinkUrl { get; set; }
/// <summary>
/// متن دکمه لینک
/// </summary>
public string? LinkText { get; set; }
}

View File

@@ -0,0 +1,67 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Entities.Message;
using MediatR;
using Microsoft.Extensions.Logging;
namespace CMSMicroservice.Application.PublicMessageCQ.Commands.CreatePublicMessage;
public class CreatePublicMessageCommandHandler : IRequestHandler<CreatePublicMessageCommand, long>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
private readonly ILogger<CreatePublicMessageCommandHandler> _logger;
public CreatePublicMessageCommandHandler(
IApplicationDbContext context,
ICurrentUserService currentUser,
ILogger<CreatePublicMessageCommandHandler> logger)
{
_context = context;
_currentUser = currentUser;
_logger = logger;
}
public async Task<long> Handle(CreatePublicMessageCommand request, CancellationToken cancellationToken)
{
// 1. بررسی Admin
var currentUserId = _currentUser.UserId;
if (string.IsNullOrEmpty(currentUserId))
{
throw new UnauthorizedAccessException("کاربر احراز هویت نشده است");
}
if (!long.TryParse(currentUserId, out var createdByUserId))
{
throw new UnauthorizedAccessException("شناسه کاربر نامعتبر است");
}
// 2. ایجاد PublicMessage
var message = new PublicMessage
{
Title = request.Title.Trim(),
Content = request.Content.Trim(),
Type = request.Type,
Priority = request.Priority,
IsActive = true,
StartsAt = request.StartsAt,
ExpiresAt = request.ExpiresAt,
CreatedByUserId = createdByUserId,
ViewCount = 0,
LinkUrl = request.LinkUrl?.Trim(),
LinkText = request.LinkText?.Trim()
};
_context.PublicMessages.Add(message);
await _context.SaveChangesAsync(cancellationToken);
_logger.LogInformation(
"Public message created successfully. Id: {Id}, Title: {Title}, Type: {Type}, CreatedBy: {CreatedBy}",
message.Id,
message.Title,
message.Type,
createdByUserId
);
return message.Id;
}
}

View File

@@ -0,0 +1,39 @@
using FluentValidation;
namespace CMSMicroservice.Application.PublicMessageCQ.Commands.CreatePublicMessage;
public class CreatePublicMessageCommandValidator : AbstractValidator<CreatePublicMessageCommand>
{
public CreatePublicMessageCommandValidator()
{
RuleFor(x => x.Title)
.NotEmpty().WithMessage("عنوان پیام الزامی است")
.MaximumLength(200).WithMessage("عنوان نمی‌تواند بیشتر از 200 کاراکتر باشد");
RuleFor(x => x.Content)
.NotEmpty().WithMessage("محتوای پیام الزامی است")
.MaximumLength(2000).WithMessage("محتوا نمی‌تواند بیشتر از 2000 کاراکتر باشد");
RuleFor(x => x.Type)
.IsInEnum().WithMessage("نوع پیام نامعتبر است");
RuleFor(x => x.Priority)
.IsInEnum().WithMessage("اولویت پیام نامعتبر است");
RuleFor(x => x.StartsAt)
.NotEmpty().WithMessage("تاریخ شروع الزامی است")
.LessThan(x => x.ExpiresAt).WithMessage("تاریخ شروع باید قبل از تاریخ پایان باشد");
RuleFor(x => x.ExpiresAt)
.NotEmpty().WithMessage("تاریخ پایان الزامی است")
.GreaterThan(DateTime.UtcNow).WithMessage("تاریخ پایان باید در آینده باشد");
RuleFor(x => x.LinkUrl)
.MaximumLength(500).WithMessage("لینک نمی‌تواند بیشتر از 500 کاراکتر باشد")
.When(x => !string.IsNullOrEmpty(x.LinkUrl));
RuleFor(x => x.LinkText)
.MaximumLength(100).WithMessage("متن لینک نمی‌تواند بیشتر از 100 کاراکتر باشد")
.When(x => !string.IsNullOrEmpty(x.LinkText));
}
}

View File

@@ -0,0 +1,15 @@
using MediatR;
namespace CMSMicroservice.Application.PublicMessageCQ.Commands.DeletePublicMessage;
/// <summary>
/// دستور حذف (soft delete) پیام عمومی
/// Admin می‌تواند پیام را حذف کند
/// </summary>
public class DeletePublicMessageCommand : IRequest<Unit>
{
/// <summary>
/// شناسه پیام
/// </summary>
public long Id { get; set; }
}

View File

@@ -0,0 +1,60 @@
using CMSMicroservice.Application.Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace CMSMicroservice.Application.PublicMessageCQ.Commands.DeletePublicMessage;
public class DeletePublicMessageCommandHandler : IRequestHandler<DeletePublicMessageCommand, Unit>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
private readonly ILogger<DeletePublicMessageCommandHandler> _logger;
public DeletePublicMessageCommandHandler(
IApplicationDbContext context,
ICurrentUserService currentUser,
ILogger<DeletePublicMessageCommandHandler> logger)
{
_context = context;
_currentUser = currentUser;
_logger = logger;
}
public async Task<Unit> Handle(DeletePublicMessageCommand request, CancellationToken cancellationToken)
{
// 1. بررسی Admin
var currentUserId = _currentUser.UserId;
if (string.IsNullOrEmpty(currentUserId))
{
throw new UnauthorizedAccessException("کاربر احراز هویت نشده است");
}
if (!long.TryParse(currentUserId, out var userId))
{
throw new UnauthorizedAccessException("شناسه کاربر نامعتبر است");
}
// 2. بررسی وجود پیام
var message = await _context.PublicMessages
.FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken);
if (message == null)
{
throw new KeyNotFoundException($"پیام با شناسه {request.Id} یافت نشد");
}
// 3. Soft Delete
message.IsDeleted = true;
await _context.SaveChangesAsync(cancellationToken);
_logger.LogInformation(
"Public message deleted successfully. Id: {Id}, DeletedBy: {DeletedBy}",
message.Id,
userId
);
return Unit.Value;
}
}

View File

@@ -0,0 +1,61 @@
using CMSMicroservice.Domain.Enums;
using MediatR;
namespace CMSMicroservice.Application.PublicMessageCQ.Commands.UpdatePublicMessage;
/// <summary>
/// دستور ویرایش پیام عمومی
/// Admin می‌تواند پیام‌های منقضی نشده را ویرایش کند
/// </summary>
public class UpdatePublicMessageCommand : IRequest<Unit>
{
/// <summary>
/// شناسه پیام
/// </summary>
public long Id { get; set; }
/// <summary>
/// عنوان پیام (حداکثر 200 کاراکتر)
/// </summary>
public string Title { get; set; } = string.Empty;
/// <summary>
/// محتوای پیام (حداکثر 2000 کاراکتر)
/// </summary>
public string Content { get; set; } = string.Empty;
/// <summary>
/// نوع پیام
/// </summary>
public MessageType Type { get; set; }
/// <summary>
/// اولویت پیام
/// </summary>
public MessagePriority Priority { get; set; }
/// <summary>
/// وضعیت فعال/غیرفعال
/// </summary>
public bool IsActive { get; set; }
/// <summary>
/// تاریخ شروع نمایش
/// </summary>
public DateTime StartsAt { get; set; }
/// <summary>
/// تاریخ پایان نمایش
/// </summary>
public DateTime ExpiresAt { get; set; }
/// <summary>
/// لینک اختیاری
/// </summary>
public string? LinkUrl { get; set; }
/// <summary>
/// متن دکمه لینک
/// </summary>
public string? LinkText { get; set; }
}

View File

@@ -0,0 +1,68 @@
using CMSMicroservice.Application.Common.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace CMSMicroservice.Application.PublicMessageCQ.Commands.UpdatePublicMessage;
public class UpdatePublicMessageCommandHandler : IRequestHandler<UpdatePublicMessageCommand, Unit>
{
private readonly IApplicationDbContext _context;
private readonly ICurrentUserService _currentUser;
private readonly ILogger<UpdatePublicMessageCommandHandler> _logger;
public UpdatePublicMessageCommandHandler(
IApplicationDbContext context,
ICurrentUserService currentUser,
ILogger<UpdatePublicMessageCommandHandler> logger)
{
_context = context;
_currentUser = currentUser;
_logger = logger;
}
public async Task<Unit> Handle(UpdatePublicMessageCommand request, CancellationToken cancellationToken)
{
// 1. بررسی Admin
var currentUserId = _currentUser.UserId;
if (string.IsNullOrEmpty(currentUserId))
{
throw new UnauthorizedAccessException("کاربر احراز هویت نشده است");
}
if (!long.TryParse(currentUserId, out var userId))
{
throw new UnauthorizedAccessException("شناسه کاربر نامعتبر است");
}
// 2. بررسی وجود پیام
var message = await _context.PublicMessages
.FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken);
if (message == null)
{
throw new KeyNotFoundException($"پیام با شناسه {request.Id} یافت نشد");
}
// 3. به‌روزرسانی پیام
message.Title = request.Title.Trim();
message.Content = request.Content.Trim();
message.Type = request.Type;
message.Priority = request.Priority;
message.IsActive = request.IsActive;
message.StartsAt = request.StartsAt;
message.ExpiresAt = request.ExpiresAt;
message.LinkUrl = request.LinkUrl?.Trim();
message.LinkText = request.LinkText?.Trim();
await _context.SaveChangesAsync(cancellationToken);
_logger.LogInformation(
"Public message updated successfully. Id: {Id}, UpdatedBy: {UpdatedBy}",
message.Id,
userId
);
return Unit.Value;
}
}

View File

@@ -0,0 +1,41 @@
using FluentValidation;
namespace CMSMicroservice.Application.PublicMessageCQ.Commands.UpdatePublicMessage;
public class UpdatePublicMessageCommandValidator : AbstractValidator<UpdatePublicMessageCommand>
{
public UpdatePublicMessageCommandValidator()
{
RuleFor(x => x.Id)
.GreaterThan(0).WithMessage("شناسه پیام نامعتبر است");
RuleFor(x => x.Title)
.NotEmpty().WithMessage("عنوان پیام الزامی است")
.MaximumLength(200).WithMessage("عنوان نمی‌تواند بیشتر از 200 کاراکتر باشد");
RuleFor(x => x.Content)
.NotEmpty().WithMessage("محتوای پیام الزامی است")
.MaximumLength(2000).WithMessage("محتوا نمی‌تواند بیشتر از 2000 کاراکتر باشد");
RuleFor(x => x.Type)
.IsInEnum().WithMessage("نوع پیام نامعتبر است");
RuleFor(x => x.Priority)
.IsInEnum().WithMessage("اولویت پیام نامعتبر است");
RuleFor(x => x.StartsAt)
.NotEmpty().WithMessage("تاریخ شروع الزامی است")
.LessThan(x => x.ExpiresAt).WithMessage("تاریخ شروع باید قبل از تاریخ پایان باشد");
RuleFor(x => x.ExpiresAt)
.NotEmpty().WithMessage("تاریخ پایان الزامی است");
RuleFor(x => x.LinkUrl)
.MaximumLength(500).WithMessage("لینک نمی‌تواند بیشتر از 500 کاراکتر باشد")
.When(x => !string.IsNullOrEmpty(x.LinkUrl));
RuleFor(x => x.LinkText)
.MaximumLength(100).WithMessage("متن لینک نمی‌تواند بیشتر از 100 کاراکتر باشد")
.When(x => !string.IsNullOrEmpty(x.LinkText));
}
}

View File

@@ -0,0 +1,13 @@
using MediatR;
namespace CMSMicroservice.Application.PublicMessageCQ.Queries.GetActiveMessages;
/// <summary>
/// کوئری دریافت پیام‌های فعال
/// برای نمایش در داشبورد کاربران
/// </summary>
public class GetActiveMessagesQuery : IRequest<List<PublicMessageDto>>
{
// فیلتر اختیاری: فقط پیام‌های با اولویت خاص
public int? MinPriority { get; set; }
}

View File

@@ -0,0 +1,91 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Domain.Enums;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace CMSMicroservice.Application.PublicMessageCQ.Queries.GetActiveMessages;
public class GetActiveMessagesQueryHandler : IRequestHandler<GetActiveMessagesQuery, List<PublicMessageDto>>
{
private readonly IApplicationDbContext _context;
private readonly ILogger<GetActiveMessagesQueryHandler> _logger;
public GetActiveMessagesQueryHandler(
IApplicationDbContext context,
ILogger<GetActiveMessagesQueryHandler> logger)
{
_context = context;
_logger = logger;
}
public async Task<List<PublicMessageDto>> Handle(GetActiveMessagesQuery request, CancellationToken cancellationToken)
{
var now = DateTime.UtcNow;
var query = _context.PublicMessages
.Where(x => !x.IsDeleted
&& x.IsActive
&& x.StartsAt <= now
&& x.ExpiresAt >= now);
// فیلتر اختیاری اولویت
if (request.MinPriority.HasValue)
{
query = query.Where(x => (int)x.Priority >= request.MinPriority.Value);
}
var messages = await query
.OrderByDescending(x => x.Priority)
.ThenByDescending(x => x.Created)
.Select(x => new PublicMessageDto
{
Id = x.Id,
Title = x.Title,
Content = x.Content,
Type = x.Type,
TypeName = GetTypeName(x.Type),
Priority = x.Priority,
PriorityName = GetPriorityName(x.Priority),
StartsAt = x.StartsAt,
ExpiresAt = x.ExpiresAt,
LinkUrl = x.LinkUrl,
LinkText = x.LinkText,
Created = x.Created
})
.ToListAsync(cancellationToken);
_logger.LogInformation(
"Retrieved {Count} active messages",
messages.Count
);
return messages;
}
private static string GetTypeName(MessageType type)
{
return type switch
{
MessageType.Announcement => "اطلاعیه",
MessageType.News => "اخبار",
MessageType.Warning => "هشدار",
MessageType.Promotion => "تبلیغات",
MessageType.SystemUpdate => "به‌روزرسانی سیستم",
MessageType.Event => "رویداد",
_ => "نامشخص"
};
}
private static string GetPriorityName(MessagePriority priority)
{
return priority switch
{
MessagePriority.Low => "کم",
MessagePriority.Medium => "متوسط",
MessagePriority.High => "بالا",
MessagePriority.Urgent => "فوری",
_ => "نامشخص"
};
}
}

View File

@@ -0,0 +1,22 @@
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.PublicMessageCQ.Queries.GetActiveMessages;
/// <summary>
/// DTO پیام عمومی برای نمایش به کاربران
/// </summary>
public class PublicMessageDto
{
public long Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Content { get; set; } = string.Empty;
public MessageType Type { get; set; }
public string TypeName { get; set; } = string.Empty;
public MessagePriority Priority { get; set; }
public string PriorityName { get; set; } = string.Empty;
public DateTime StartsAt { get; set; }
public DateTime ExpiresAt { get; set; }
public string? LinkUrl { get; set; }
public string? LinkText { get; set; }
public DateTime Created { get; set; }
}

View File

@@ -0,0 +1,27 @@
using CMSMicroservice.Domain.Enums;
namespace CMSMicroservice.Application.PublicMessageCQ.Queries.GetAllMessages;
/// <summary>
/// DTO پیام عمومی برای Admin (با اطلاعات بیشتر)
/// </summary>
public class AdminPublicMessageDto
{
public long Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Content { get; set; } = string.Empty;
public MessageType Type { get; set; }
public string TypeName { get; set; } = string.Empty;
public MessagePriority Priority { get; set; }
public string PriorityName { get; set; } = string.Empty;
public bool IsActive { get; set; }
public DateTime StartsAt { get; set; }
public DateTime ExpiresAt { get; set; }
public long CreatedByUserId { get; set; }
public int ViewCount { get; set; }
public string? LinkUrl { get; set; }
public string? LinkText { get; set; }
public DateTime Created { get; set; }
public DateTime? LastModified { get; set; }
public bool IsExpired { get; set; }
}

View File

@@ -0,0 +1,24 @@
using CMSMicroservice.Application.Common.Models;
using CMSMicroservice.Domain.Enums;
using MediatR;
namespace CMSMicroservice.Application.PublicMessageCQ.Queries.GetAllMessages;
/// <summary>
/// کوئری دریافت همه پیام‌ها (Admin)
/// با فیلترها و صفحه‌بندی
/// </summary>
public class GetAllMessagesQuery : IRequest<GetAllMessagesResponseDto>
{
public int PageNumber { get; set; } = 1;
public int PageSize { get; set; } = 10;
// فیلترها
public bool? IsActive { get; set; }
public MessageType? Type { get; set; }
public MessagePriority? Priority { get; set; }
public DateTime? StartDate { get; set; }
public DateTime? EndDate { get; set; }
public string? SearchTerm { get; set; }
public bool OrderByDescending { get; set; } = true;
}

View File

@@ -0,0 +1,146 @@
using CMSMicroservice.Application.Common.Interfaces;
using CMSMicroservice.Application.Common.Models;
using CMSMicroservice.Domain.Enums;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace CMSMicroservice.Application.PublicMessageCQ.Queries.GetAllMessages;
public class GetAllMessagesQueryHandler : IRequestHandler<GetAllMessagesQuery, GetAllMessagesResponseDto>
{
private readonly IApplicationDbContext _context;
private readonly ILogger<GetAllMessagesQueryHandler> _logger;
public GetAllMessagesQueryHandler(
IApplicationDbContext context,
ILogger<GetAllMessagesQueryHandler> logger)
{
_context = context;
_logger = logger;
}
public async Task<GetAllMessagesResponseDto> Handle(GetAllMessagesQuery request, CancellationToken cancellationToken)
{
var now = DateTime.UtcNow;
// Query پایه
var query = _context.PublicMessages
.Where(x => !x.IsDeleted);
// فیلترها
if (request.IsActive.HasValue)
{
query = query.Where(x => x.IsActive == request.IsActive.Value);
}
if (request.Type.HasValue)
{
query = query.Where(x => x.Type == request.Type.Value);
}
if (request.Priority.HasValue)
{
query = query.Where(x => x.Priority == request.Priority.Value);
}
if (request.StartDate.HasValue)
{
query = query.Where(x => x.Created >= request.StartDate.Value);
}
if (request.EndDate.HasValue)
{
query = query.Where(x => x.Created <= request.EndDate.Value);
}
if (!string.IsNullOrEmpty(request.SearchTerm))
{
var searchTerm = request.SearchTerm.ToLower();
query = query.Where(x => x.Title.ToLower().Contains(searchTerm)
|| x.Content.ToLower().Contains(searchTerm));
}
// تعداد کل
var totalCount = await query.CountAsync(cancellationToken);
// مرتب‌سازی
query = request.OrderByDescending
? query.OrderByDescending(x => x.Created)
: query.OrderBy(x => x.Created);
// صفحه‌بندی
var messages = await query
.Skip((request.PageNumber - 1) * request.PageSize)
.Take(request.PageSize)
.Select(x => new AdminPublicMessageDto
{
Id = x.Id,
Title = x.Title,
Content = x.Content,
Type = x.Type,
TypeName = GetTypeName(x.Type),
Priority = x.Priority,
PriorityName = GetPriorityName(x.Priority),
IsActive = x.IsActive,
StartsAt = x.StartsAt,
ExpiresAt = x.ExpiresAt,
CreatedByUserId = x.CreatedByUserId,
ViewCount = x.ViewCount,
LinkUrl = x.LinkUrl,
LinkText = x.LinkText,
Created = x.Created,
LastModified = x.LastModified,
IsExpired = x.ExpiresAt < now
})
.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} messages. Total: {Total}",
messages.Count,
totalCount
);
return new GetAllMessagesResponseDto
{
MetaData = metaData,
Messages = messages
};
}
private static string GetTypeName(MessageType type)
{
return type switch
{
MessageType.Announcement => "اطلاعیه",
MessageType.News => "اخبار",
MessageType.Warning => "هشدار",
MessageType.Promotion => "تبلیغات",
MessageType.SystemUpdate => "به‌روزرسانی سیستم",
MessageType.Event => "رویداد",
_ => "نامشخص"
};
}
private static string GetPriorityName(MessagePriority priority)
{
return priority switch
{
MessagePriority.Low => "کم",
MessagePriority.Medium => "متوسط",
MessagePriority.High => "بالا",
MessagePriority.Urgent => "فوری",
_ => "نامشخص"
};
}
}

View File

@@ -0,0 +1,13 @@
using CMSMicroservice.Application.Common.Models;
using CMSMicroservice.Application.PublicMessageCQ.Queries.GetActiveMessages;
namespace CMSMicroservice.Application.PublicMessageCQ.Queries.GetAllMessages;
/// <summary>
/// Response DTO برای لیست پیام‌ها با متادیتا
/// </summary>
public class GetAllMessagesResponseDto
{
public MetaData MetaData { get; set; } = new();
public List<AdminPublicMessageDto> Messages { get; set; } = new();
}