diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/CreateBlogCategory/CreateBlogCategoryCommand.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/CreateBlogCategory/CreateBlogCategoryCommand.cs new file mode 100644 index 0000000..3cee723 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/CreateBlogCategory/CreateBlogCategoryCommand.cs @@ -0,0 +1,13 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Commands.CreateBlogCategory; + +public class CreateBlogCategoryCommand : IRequest +{ + public string Title { get; set; } = default!; + public string Slug { get; set; } = default!; + public string? Description { get; set; } + public string? IconName { get; set; } + public int SortOrder { get; set; } + public bool IsActive { get; set; } = true; +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/CreateBlogCategory/CreateBlogCategoryCommandHandler.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/CreateBlogCategory/CreateBlogCategoryCommandHandler.cs new file mode 100644 index 0000000..87afeda --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/CreateBlogCategory/CreateBlogCategoryCommandHandler.cs @@ -0,0 +1,33 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Blog; +using MediatR; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Commands.CreateBlogCategory; + +public class CreateBlogCategoryCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public CreateBlogCategoryCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(CreateBlogCategoryCommand request, CancellationToken cancellationToken) + { + var entity = new BlogCategory + { + Title = request.Title, + Slug = request.Slug.ToLower(), + Description = request.Description, + IconName = request.IconName, + SortOrder = request.SortOrder, + IsActive = request.IsActive + }; + + _context.BlogCategories.Add(entity); + await _context.SaveChangesAsync(cancellationToken); + + return entity.Id; + } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/CreateBlogCategory/CreateBlogCategoryCommandValidator.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/CreateBlogCategory/CreateBlogCategoryCommandValidator.cs new file mode 100644 index 0000000..ab46f3e --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/CreateBlogCategory/CreateBlogCategoryCommandValidator.cs @@ -0,0 +1,24 @@ +using FluentValidation; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Commands.CreateBlogCategory; + +public class CreateBlogCategoryCommandValidator : AbstractValidator +{ + public CreateBlogCategoryCommandValidator() + { + RuleFor(x => x.Title) + .NotEmpty().WithMessage("عنوان دسته‌بندی الزامی است") + .MaximumLength(200).WithMessage("عنوان دسته‌بندی حداکثر ۲۰۰ کاراکتر"); + + RuleFor(x => x.Slug) + .NotEmpty().WithMessage("اسلاگ الزامی است") + .MaximumLength(200).WithMessage("اسلاگ حداکثر ۲۰۰ کاراکتر") + .Matches(@"^[a-z0-9\-]+$").WithMessage("اسلاگ فقط شامل حروف کوچک، اعداد و خط تیره"); + + RuleFor(x => x.Description) + .MaximumLength(1000).WithMessage("توضیحات حداکثر ۱۰۰۰ کاراکتر"); + + RuleFor(x => x.IconName) + .MaximumLength(100).WithMessage("نام آیکون حداکثر ۱۰۰ کاراکتر"); + } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/DeleteBlogCategory/DeleteBlogCategoryCommand.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/DeleteBlogCategory/DeleteBlogCategoryCommand.cs new file mode 100644 index 0000000..8446cee --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/DeleteBlogCategory/DeleteBlogCategoryCommand.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Commands.DeleteBlogCategory; + +public class DeleteBlogCategoryCommand : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/DeleteBlogCategory/DeleteBlogCategoryCommandHandler.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/DeleteBlogCategory/DeleteBlogCategoryCommandHandler.cs new file mode 100644 index 0000000..63f0545 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/DeleteBlogCategory/DeleteBlogCategoryCommandHandler.cs @@ -0,0 +1,29 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Blog; +using MediatR; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Commands.DeleteBlogCategory; + +public class DeleteBlogCategoryCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public DeleteBlogCategoryCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(DeleteBlogCategoryCommand request, CancellationToken cancellationToken) + { + var entity = await _context.BlogCategories.FindAsync(new object[] { request.Id }, cancellationToken); + if (entity == null || entity.IsDeleted) + throw new NotFoundException(nameof(BlogCategory), request.Id); + + entity.IsDeleted = true; + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/UpdateBlogCategory/UpdateBlogCategoryCommand.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/UpdateBlogCategory/UpdateBlogCategoryCommand.cs new file mode 100644 index 0000000..17f8bc0 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/UpdateBlogCategory/UpdateBlogCategoryCommand.cs @@ -0,0 +1,14 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Commands.UpdateBlogCategory; + +public class UpdateBlogCategoryCommand : IRequest +{ + public long Id { get; set; } + public string Title { get; set; } = default!; + public string Slug { get; set; } = default!; + public string? Description { get; set; } + public string? IconName { get; set; } + public int SortOrder { get; set; } + public bool IsActive { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/UpdateBlogCategory/UpdateBlogCategoryCommandHandler.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/UpdateBlogCategory/UpdateBlogCategoryCommandHandler.cs new file mode 100644 index 0000000..74d7f2b --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/UpdateBlogCategory/UpdateBlogCategoryCommandHandler.cs @@ -0,0 +1,34 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Blog; +using MediatR; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Commands.UpdateBlogCategory; + +public class UpdateBlogCategoryCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public UpdateBlogCategoryCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(UpdateBlogCategoryCommand request, CancellationToken cancellationToken) + { + var entity = await _context.BlogCategories.FindAsync(new object[] { request.Id }, cancellationToken); + if (entity == null || entity.IsDeleted) + throw new NotFoundException(nameof(BlogCategory), request.Id); + + entity.Title = request.Title; + entity.Slug = request.Slug.ToLower(); + entity.Description = request.Description; + entity.IconName = request.IconName; + entity.SortOrder = request.SortOrder; + entity.IsActive = request.IsActive; + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/UpdateBlogCategory/UpdateBlogCategoryCommandValidator.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/UpdateBlogCategory/UpdateBlogCategoryCommandValidator.cs new file mode 100644 index 0000000..2414301 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Commands/UpdateBlogCategory/UpdateBlogCategoryCommandValidator.cs @@ -0,0 +1,27 @@ +using FluentValidation; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Commands.UpdateBlogCategory; + +public class UpdateBlogCategoryCommandValidator : AbstractValidator +{ + public UpdateBlogCategoryCommandValidator() + { + RuleFor(x => x.Id) + .GreaterThan(0).WithMessage("شناسه دسته‌بندی نامعتبر است"); + + RuleFor(x => x.Title) + .NotEmpty().WithMessage("عنوان دسته‌بندی الزامی است") + .MaximumLength(200).WithMessage("عنوان دسته‌بندی حداکثر ۲۰۰ کاراکتر"); + + RuleFor(x => x.Slug) + .NotEmpty().WithMessage("اسلاگ الزامی است") + .MaximumLength(200).WithMessage("اسلاگ حداکثر ۲۰۰ کاراکتر") + .Matches(@"^[a-z0-9\-]+$").WithMessage("اسلاگ فقط شامل حروف کوچک، اعداد و خط تیره"); + + RuleFor(x => x.Description) + .MaximumLength(1000).WithMessage("توضیحات حداکثر ۱۰۰۰ کاراکتر"); + + RuleFor(x => x.IconName) + .MaximumLength(100).WithMessage("نام آیکون حداکثر ۱۰۰ کاراکتر"); + } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetActiveBlogCategories/GetActiveBlogCategoriesQuery.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetActiveBlogCategories/GetActiveBlogCategoriesQuery.cs new file mode 100644 index 0000000..553dbd4 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetActiveBlogCategories/GetActiveBlogCategoriesQuery.cs @@ -0,0 +1,45 @@ +using CMSMicroservice.Application.BlogCategoryCQ.Queries.GetBlogCategory; +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Queries.GetActiveBlogCategories; + +public class GetActiveBlogCategoriesQuery : IRequest> +{ +} + +public class GetActiveBlogCategoriesQueryHandler : IRequestHandler> +{ + private readonly IApplicationDbContext _context; + + public GetActiveBlogCategoriesQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task> Handle(GetActiveBlogCategoriesQuery request, CancellationToken cancellationToken) + { + var categories = await _context.BlogCategories + .Include(x => x.BlogPostCategories) + .Where(x => !x.IsDeleted && x.IsActive) + .OrderBy(x => x.SortOrder) + .ThenBy(x => x.Title) + .Select(x => new BlogCategoryDto + { + Id = x.Id, + Title = x.Title, + Slug = x.Slug, + Description = x.Description, + IconName = x.IconName, + SortOrder = x.SortOrder, + IsActive = x.IsActive, + PostCount = x.BlogPostCategories.Count, + Created = x.Created, + LastModified = x.LastModified + }) + .ToListAsync(cancellationToken); + + return categories; + } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetAllBlogCategories/GetAllBlogCategoriesQuery.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetAllBlogCategories/GetAllBlogCategoriesQuery.cs new file mode 100644 index 0000000..e13bb77 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetAllBlogCategories/GetAllBlogCategoriesQuery.cs @@ -0,0 +1,11 @@ +using CMSMicroservice.Application.Common.Models; +using MediatR; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Queries.GetAllBlogCategories; + +public class GetAllBlogCategoriesQuery : IRequest +{ + public int PageNumber { get; set; } = 1; + public int PageSize { get; set; } = 20; + public string? SearchTerm { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetAllBlogCategories/GetAllBlogCategoriesQueryHandler.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetAllBlogCategories/GetAllBlogCategoriesQueryHandler.cs new file mode 100644 index 0000000..c1d2545 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetAllBlogCategories/GetAllBlogCategoriesQueryHandler.cs @@ -0,0 +1,64 @@ +using CMSMicroservice.Application.BlogCategoryCQ.Queries.GetBlogCategory; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Models; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Queries.GetAllBlogCategories; + +public class GetAllBlogCategoriesQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetAllBlogCategoriesQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetAllBlogCategoriesQuery request, CancellationToken cancellationToken) + { + var query = _context.BlogCategories + .Include(x => x.BlogPostCategories) + .Where(x => !x.IsDeleted); + + if (!string.IsNullOrEmpty(request.SearchTerm)) + { + var term = request.SearchTerm.ToLower(); + query = query.Where(x => x.Title.ToLower().Contains(term) || x.Slug.ToLower().Contains(term)); + } + + var totalCount = await query.CountAsync(cancellationToken); + + var categories = await query + .OrderBy(x => x.SortOrder) + .ThenBy(x => x.Title) + .Skip((request.PageNumber - 1) * request.PageSize) + .Take(request.PageSize) + .Select(x => new BlogCategoryDto + { + Id = x.Id, + Title = x.Title, + Slug = x.Slug, + Description = x.Description, + IconName = x.IconName, + SortOrder = x.SortOrder, + IsActive = x.IsActive, + PostCount = x.BlogPostCategories.Count, + Created = x.Created, + LastModified = x.LastModified + }) + .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 + }; + + return new GetAllBlogCategoriesResponseDto { MetaData = metaData, Models = categories }; + } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetAllBlogCategories/GetAllBlogCategoriesResponseDto.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetAllBlogCategories/GetAllBlogCategoriesResponseDto.cs new file mode 100644 index 0000000..71d1967 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetAllBlogCategories/GetAllBlogCategoriesResponseDto.cs @@ -0,0 +1,10 @@ +using CMSMicroservice.Application.BlogCategoryCQ.Queries.GetBlogCategory; +using CMSMicroservice.Application.Common.Models; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Queries.GetAllBlogCategories; + +public class GetAllBlogCategoriesResponseDto +{ + public MetaData MetaData { get; set; } = default!; + public List Models { get; set; } = new(); +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetBlogCategory/BlogCategoryDto.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetBlogCategory/BlogCategoryDto.cs new file mode 100644 index 0000000..e858630 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetBlogCategory/BlogCategoryDto.cs @@ -0,0 +1,15 @@ +namespace CMSMicroservice.Application.BlogCategoryCQ.Queries.GetBlogCategory; + +public class BlogCategoryDto +{ + public long Id { get; set; } + public string Title { get; set; } = default!; + public string Slug { get; set; } = default!; + public string? Description { get; set; } + public string? IconName { get; set; } + public int SortOrder { get; set; } + public bool IsActive { get; set; } + public int PostCount { get; set; } + public DateTime Created { get; set; } + public DateTime? LastModified { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetBlogCategory/GetBlogCategoryQuery.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetBlogCategory/GetBlogCategoryQuery.cs new file mode 100644 index 0000000..3cc5ce7 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetBlogCategory/GetBlogCategoryQuery.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Queries.GetBlogCategory; + +public class GetBlogCategoryQuery : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetBlogCategory/GetBlogCategoryQueryHandler.cs b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetBlogCategory/GetBlogCategoryQueryHandler.cs new file mode 100644 index 0000000..44d3672 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogCategoryCQ/Queries/GetBlogCategory/GetBlogCategoryQueryHandler.cs @@ -0,0 +1,41 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Blog; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogCategoryCQ.Queries.GetBlogCategory; + +public class GetBlogCategoryQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetBlogCategoryQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetBlogCategoryQuery request, CancellationToken cancellationToken) + { + var entity = await _context.BlogCategories + .Include(x => x.BlogPostCategories) + .FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken); + + if (entity == null) + throw new NotFoundException(nameof(BlogCategory), request.Id); + + return new BlogCategoryDto + { + Id = entity.Id, + Title = entity.Title, + Slug = entity.Slug, + Description = entity.Description, + IconName = entity.IconName, + SortOrder = entity.SortOrder, + IsActive = entity.IsActive, + PostCount = entity.BlogPostCategories.Count, + Created = entity.Created, + LastModified = entity.LastModified + }; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/ArchiveBlogPost/ArchiveBlogPostCommand.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/ArchiveBlogPost/ArchiveBlogPostCommand.cs new file mode 100644 index 0000000..84364d7 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/ArchiveBlogPost/ArchiveBlogPostCommand.cs @@ -0,0 +1,14 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.ArchiveBlogPost; + +public class ArchiveBlogPostCommand : IRequest +{ + public long Id { get; set; } +} + +public class ArchiveBlogPostResult +{ + public bool Success { get; set; } + public string Message { get; set; } = string.Empty; +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/ArchiveBlogPost/ArchiveBlogPostCommandHandler.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/ArchiveBlogPost/ArchiveBlogPostCommandHandler.cs new file mode 100644 index 0000000..d81a33d --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/ArchiveBlogPost/ArchiveBlogPostCommandHandler.cs @@ -0,0 +1,32 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Enums; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.ArchiveBlogPost; + +public class ArchiveBlogPostCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ILogger _logger; + + public ArchiveBlogPostCommandHandler(IApplicationDbContext context, ILogger logger) + { + _context = context; + _logger = logger; + } + + public async Task Handle(ArchiveBlogPostCommand request, CancellationToken cancellationToken) + { + var post = await _context.BlogPosts.FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken) + ?? throw new KeyNotFoundException($"مقاله با شناسه {request.Id} یافت نشد"); + + post.Status = BlogPostStatus.Archived; + await _context.SaveChangesAsync(cancellationToken); + + _logger.LogInformation("Blog post archived. Id: {Id}, Title: {Title}", post.Id, post.Title); + + return new ArchiveBlogPostResult { Success = true, Message = "مقاله آرشیو شد" }; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommand.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommand.cs new file mode 100644 index 0000000..691dcb7 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommand.cs @@ -0,0 +1,25 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.CreateBlogPost; + +/// +/// دستور ایجاد مقاله جدید +/// +public class CreateBlogPostCommand : IRequest +{ + public string Title { get; set; } = string.Empty; + public string Slug { get; set; } = string.Empty; + public string? Summary { get; set; } + public string HtmlContent { get; set; } = string.Empty; + public string? FeaturedImagePath { get; set; } + public string? FeaturedImageThumbnailPath { get; set; } + public List CategoryIds { get; set; } = new(); + public List TagIds { get; set; } = new(); + public bool IsFeatured { get; set; } + public int SortOrder { get; set; } + + // Image upload properties + public byte[]? ImageFileBytes { get; set; } + public string? ImageFileMime { get; set; } + public string? ImageFileName { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommandHandler.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommandHandler.cs new file mode 100644 index 0000000..9bb72e6 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommandHandler.cs @@ -0,0 +1,98 @@ +using CMSMicroservice.Application.Common.FileManager; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Blog; +using CMSMicroservice.Domain.Enums; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.CreateBlogPost; + +public class CreateBlogPostCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ICurrentUserService _currentUser; + private readonly IFileManager _fileManager; + private readonly ILogger _logger; + + public CreateBlogPostCommandHandler( + IApplicationDbContext context, + ICurrentUserService currentUser, + IFileManager fileManager, + ILogger logger) + { + _context = context; + _currentUser = currentUser; + _fileManager = fileManager; + _logger = logger; + } + + public async Task Handle(CreateBlogPostCommand request, CancellationToken cancellationToken) + { + var currentUserId = _currentUser.UserId; + if (string.IsNullOrEmpty(currentUserId) || !long.TryParse(currentUserId, out var authorUserId)) + throw new UnauthorizedAccessException("کاربر احراز هویت نشده است"); + + var post = new BlogPost + { + Title = request.Title.Trim(), + Slug = string.IsNullOrWhiteSpace(request.Slug) + ? $"post-{Guid.NewGuid():N}".Substring(0, 20) + : request.Slug.Trim().ToLower(), + Summary = request.Summary?.Trim(), + HtmlContent = request.HtmlContent, + FeaturedImagePath = request.FeaturedImagePath, + FeaturedImageThumbnailPath = request.FeaturedImageThumbnailPath, + Status = BlogPostStatus.Draft, + AuthorUserId = authorUserId, + IsFeatured = request.IsFeatured, + SortOrder = request.SortOrder, + ViewCount = 0 + }; + + // آپلود تصویر شاخص (اگر فایل ارسال شده باشد) + if (request.ImageFileBytes is { Length: > 0 }) + { + var result = await _fileManager.UploadImageAsync( + "Images/BlogPosts", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + post.FeaturedImagePath = result.Main.Path; + post.FeaturedImageThumbnailPath = result.Thumbnail.Path; + } + + _context.BlogPosts.Add(post); + await _context.SaveChangesAsync(cancellationToken); + + // افزودن دسته‌بندی‌ها + foreach (var categoryId in request.CategoryIds) + { + _context.BlogPostCategories.Add(new BlogPostCategory + { + BlogPostId = post.Id, + BlogCategoryId = categoryId + }); + } + + // افزودن تگ‌ها + foreach (var tagId in request.TagIds) + { + _context.BlogPostTags.Add(new BlogPostTag + { + BlogPostId = post.Id, + TagId = tagId + }); + } + + if (request.CategoryIds.Any() || request.TagIds.Any()) + await _context.SaveChangesAsync(cancellationToken); + + _logger.LogInformation( + "Blog post created. Id: {Id}, Title: {Title}, Author: {Author}", + post.Id, post.Title, authorUserId); + + return post.Id; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommandValidator.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommandValidator.cs new file mode 100644 index 0000000..4ba5dc3 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/CreateBlogPost/CreateBlogPostCommandValidator.cs @@ -0,0 +1,25 @@ +using FluentValidation; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.CreateBlogPost; + +public class CreateBlogPostCommandValidator : AbstractValidator +{ + public CreateBlogPostCommandValidator() + { + RuleFor(x => x.Title) + .NotEmpty().WithMessage("عنوان مقاله الزامی است") + .MaximumLength(200).WithMessage("عنوان نمی‌تواند بیشتر از 200 کاراکتر باشد"); + + RuleFor(x => x.Slug) + .NotEmpty().WithMessage("نشانی یکتا (slug) الزامی است") + .MaximumLength(200).WithMessage("نشانی نمی‌تواند بیشتر از 200 کاراکتر باشد") + .Matches(@"^[a-z0-9\-]+$").WithMessage("نشانی فقط می‌تواند شامل حروف کوچک انگلیسی، اعداد و خط تیره باشد"); + + RuleFor(x => x.Summary) + .MaximumLength(500).WithMessage("خلاصه نمی‌تواند بیشتر از 500 کاراکتر باشد") + .When(x => !string.IsNullOrEmpty(x.Summary)); + + RuleFor(x => x.HtmlContent) + .NotEmpty().WithMessage("محتوای مقاله الزامی است"); + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/DeleteBlogPost/DeleteBlogPostCommand.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/DeleteBlogPost/DeleteBlogPostCommand.cs new file mode 100644 index 0000000..4d4df07 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/DeleteBlogPost/DeleteBlogPostCommand.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.DeleteBlogPost; + +public class DeleteBlogPostCommand : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/DeleteBlogPost/DeleteBlogPostCommandHandler.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/DeleteBlogPost/DeleteBlogPostCommandHandler.cs new file mode 100644 index 0000000..f646348 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/DeleteBlogPost/DeleteBlogPostCommandHandler.cs @@ -0,0 +1,31 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.DeleteBlogPost; + +public class DeleteBlogPostCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ILogger _logger; + + public DeleteBlogPostCommandHandler(IApplicationDbContext context, ILogger logger) + { + _context = context; + _logger = logger; + } + + public async Task Handle(DeleteBlogPostCommand request, CancellationToken cancellationToken) + { + var post = await _context.BlogPosts.FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken) + ?? throw new KeyNotFoundException($"مقاله با شناسه {request.Id} یافت نشد"); + + post.IsDeleted = true; + await _context.SaveChangesAsync(cancellationToken); + + _logger.LogInformation("Blog post soft-deleted. Id: {Id}", post.Id); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/IncrementViewCount/IncrementViewCountCommand.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/IncrementViewCount/IncrementViewCountCommand.cs new file mode 100644 index 0000000..52a56ba --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/IncrementViewCount/IncrementViewCountCommand.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.IncrementViewCount; + +public class IncrementViewCountCommand : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/IncrementViewCount/IncrementViewCountCommandHandler.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/IncrementViewCount/IncrementViewCountCommandHandler.cs new file mode 100644 index 0000000..a842984 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/IncrementViewCount/IncrementViewCountCommandHandler.cs @@ -0,0 +1,27 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.IncrementViewCount; + +public class IncrementViewCountCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public IncrementViewCountCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(IncrementViewCountCommand request, CancellationToken cancellationToken) + { + var post = await _context.BlogPosts.FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken); + if (post != null) + { + post.ViewCount++; + await _context.SaveChangesAsync(cancellationToken); + } + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/PublishBlogPost/PublishBlogPostCommand.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/PublishBlogPost/PublishBlogPostCommand.cs new file mode 100644 index 0000000..3114b3f --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/PublishBlogPost/PublishBlogPostCommand.cs @@ -0,0 +1,15 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.PublishBlogPost; + +public class PublishBlogPostCommand : IRequest +{ + public long Id { get; set; } +} + +public class PublishBlogPostResult +{ + public bool Success { get; set; } + public string Message { get; set; } = string.Empty; + public DateTime? PublishedAt { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/PublishBlogPost/PublishBlogPostCommandHandler.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/PublishBlogPost/PublishBlogPostCommandHandler.cs new file mode 100644 index 0000000..5f19f60 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/PublishBlogPost/PublishBlogPostCommandHandler.cs @@ -0,0 +1,41 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Enums; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.PublishBlogPost; + +public class PublishBlogPostCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly ILogger _logger; + + public PublishBlogPostCommandHandler(IApplicationDbContext context, ILogger logger) + { + _context = context; + _logger = logger; + } + + public async Task Handle(PublishBlogPostCommand request, CancellationToken cancellationToken) + { + var post = await _context.BlogPosts.FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken) + ?? throw new KeyNotFoundException($"مقاله با شناسه {request.Id} یافت نشد"); + + if (post.Status == BlogPostStatus.Published) + return new PublishBlogPostResult { Success = false, Message = "مقاله قبلاً منتشر شده است" }; + + post.Status = BlogPostStatus.Published; + post.PublishedAt = DateTime.UtcNow; + await _context.SaveChangesAsync(cancellationToken); + + _logger.LogInformation("Blog post published. Id: {Id}, Title: {Title}", post.Id, post.Title); + + return new PublishBlogPostResult + { + Success = true, + Message = "مقاله با موفقیت منتشر شد", + PublishedAt = post.PublishedAt + }; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommand.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommand.cs new file mode 100644 index 0000000..59121a2 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommand.cs @@ -0,0 +1,23 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.UpdateBlogPost; + +public class UpdateBlogPostCommand : IRequest +{ + public long Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Slug { get; set; } = string.Empty; + public string? Summary { get; set; } + public string HtmlContent { get; set; } = string.Empty; + public string? FeaturedImagePath { get; set; } + public string? FeaturedImageThumbnailPath { get; set; } + public List CategoryIds { get; set; } = new(); + public List TagIds { get; set; } = new(); + public bool IsFeatured { get; set; } + public int SortOrder { get; set; } + + // Image upload properties + public byte[]? ImageFileBytes { get; set; } + public string? ImageFileMime { get; set; } + public string? ImageFileName { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommandHandler.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommandHandler.cs new file mode 100644 index 0000000..8b9c74e --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommandHandler.cs @@ -0,0 +1,88 @@ +using CMSMicroservice.Application.Common.FileManager; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Blog; +using MediatR; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.UpdateBlogPost; + +public class UpdateBlogPostCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IFileManager _fileManager; + private readonly ILogger _logger; + + public UpdateBlogPostCommandHandler( + IApplicationDbContext context, + IFileManager fileManager, + ILogger logger) + { + _context = context; + _fileManager = fileManager; + _logger = logger; + } + + public async Task Handle(UpdateBlogPostCommand request, CancellationToken cancellationToken) + { + var post = await _context.BlogPosts.FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken) + ?? throw new KeyNotFoundException($"مقاله با شناسه {request.Id} یافت نشد"); + + post.Title = request.Title.Trim(); + // حفظ slug قبلی اگر مقدار جدید خالی باشد + if (!string.IsNullOrWhiteSpace(request.Slug)) + post.Slug = request.Slug.Trim().ToLower(); + post.Summary = request.Summary?.Trim(); + post.HtmlContent = request.HtmlContent; + post.FeaturedImagePath = request.FeaturedImagePath; + post.FeaturedImageThumbnailPath = request.FeaturedImageThumbnailPath; + post.IsFeatured = request.IsFeatured; + post.SortOrder = request.SortOrder; + + // آپلود تصویر شاخص (اگر فایل جدید ارسال شده باشد) + if (request.ImageFileBytes is { Length: > 0 }) + { + var result = await _fileManager.UploadImageAsync( + "Images/BlogPosts", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + post.FeaturedImagePath = result.Main.Path; + post.FeaturedImageThumbnailPath = result.Thumbnail.Path; + } + + // بروزرسانی دسته‌بندی‌ها + var existingCategories = await _context.BlogPostCategories + .Where(x => x.BlogPostId == post.Id).ToListAsync(cancellationToken); + _context.BlogPostCategories.RemoveRange(existingCategories); + foreach (var categoryId in request.CategoryIds) + { + _context.BlogPostCategories.Add(new BlogPostCategory + { + BlogPostId = post.Id, + BlogCategoryId = categoryId + }); + } + + // بروزرسانی تگ‌ها + var existingTags = await _context.BlogPostTags + .Where(x => x.BlogPostId == post.Id).ToListAsync(cancellationToken); + _context.BlogPostTags.RemoveRange(existingTags); + foreach (var tagId in request.TagIds) + { + _context.BlogPostTags.Add(new BlogPostTag + { + BlogPostId = post.Id, + TagId = tagId + }); + } + + await _context.SaveChangesAsync(cancellationToken); + + _logger.LogInformation("Blog post updated. Id: {Id}, Title: {Title}", post.Id, post.Title); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommandValidator.cs b/src/CMSMicroservice.Application/BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommandValidator.cs new file mode 100644 index 0000000..7359f03 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Commands/UpdateBlogPost/UpdateBlogPostCommandValidator.cs @@ -0,0 +1,23 @@ +using FluentValidation; + +namespace CMSMicroservice.Application.BlogPostCQ.Commands.UpdateBlogPost; + +public class UpdateBlogPostCommandValidator : AbstractValidator +{ + public UpdateBlogPostCommandValidator() + { + RuleFor(x => x.Id).GreaterThan(0).WithMessage("شناسه مقاله نامعتبر است"); + + RuleFor(x => x.Title) + .NotEmpty().WithMessage("عنوان مقاله الزامی است") + .MaximumLength(200).WithMessage("عنوان نمی‌تواند بیشتر از 200 کاراکتر باشد"); + + RuleFor(x => x.Slug) + .NotEmpty().WithMessage("نشانی یکتا (slug) الزامی است") + .MaximumLength(200).WithMessage("نشانی نمی‌تواند بیشتر از 200 کاراکتر باشد") + .Matches(@"^[a-z0-9\-]+$").WithMessage("نشانی فقط می‌تواند شامل حروف کوچک انگلیسی، اعداد و خط تیره باشد"); + + RuleFor(x => x.HtmlContent) + .NotEmpty().WithMessage("محتوای مقاله الزامی است"); + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetAllBlogPosts/GetAllBlogPostsQuery.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetAllBlogPosts/GetAllBlogPostsQuery.cs new file mode 100644 index 0000000..828db51 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetAllBlogPosts/GetAllBlogPostsQuery.cs @@ -0,0 +1,16 @@ +using CMSMicroservice.Application.Common.Models; +using CMSMicroservice.Domain.Enums; +using MediatR; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetAllBlogPosts; + +public class GetAllBlogPostsQuery : IRequest +{ + public int PageNumber { get; set; } = 1; + public int PageSize { get; set; } = 10; + public string? SortBy { get; set; } + public string? SearchTerm { get; set; } + public BlogPostStatus? Status { get; set; } + public long? CategoryId { get; set; } + public bool? IsFeatured { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetAllBlogPosts/GetAllBlogPostsQueryHandler.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetAllBlogPosts/GetAllBlogPostsQueryHandler.cs new file mode 100644 index 0000000..c6e416e --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetAllBlogPosts/GetAllBlogPostsQueryHandler.cs @@ -0,0 +1,85 @@ +using CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPost; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Models; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetAllBlogPosts; + +public class GetAllBlogPostsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetAllBlogPostsQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetAllBlogPostsQuery request, CancellationToken cancellationToken) + { + var query = _context.BlogPosts + .Include(x => x.BlogPostCategories).ThenInclude(x => x.BlogCategory) + .Where(x => !x.IsDeleted); + + // فیلترها + if (request.Status.HasValue) + query = query.Where(x => x.Status == request.Status.Value); + if (request.CategoryId.HasValue) + query = query.Where(x => x.BlogPostCategories.Any(c => c.BlogCategoryId == request.CategoryId.Value)); + if (request.IsFeatured.HasValue) + query = query.Where(x => x.IsFeatured == request.IsFeatured.Value); + if (!string.IsNullOrEmpty(request.SearchTerm)) + { + var term = request.SearchTerm.ToLower(); + query = query.Where(x => x.Title.ToLower().Contains(term) || (x.Summary != null && x.Summary.ToLower().Contains(term))); + } + + var totalCount = await query.CountAsync(cancellationToken); + + // مرتب‌سازی + query = request.SortBy?.ToLower() switch + { + "title" => query.OrderBy(x => x.Title), + "viewcount" => query.OrderByDescending(x => x.ViewCount), + "publishedat" => query.OrderByDescending(x => x.PublishedAt), + _ => query.OrderByDescending(x => x.Created) + }; + + var posts = await query + .Skip((request.PageNumber - 1) * request.PageSize) + .Take(request.PageSize) + .Select(x => new BlogPostListItemDto + { + Id = x.Id, + Title = x.Title, + Slug = x.Slug, + Summary = x.Summary, + FeaturedImageThumbnailPath = x.FeaturedImageThumbnailPath, + Status = (int)x.Status, + StatusName = GetBlogPostQueryHandler.GetStatusName(x.Status), + PublishedAt = x.PublishedAt, + ViewCount = x.ViewCount, + IsFeatured = x.IsFeatured, + Created = x.Created, + Categories = x.BlogPostCategories.Select(c => new BlogPostCategoryDto + { + Id = c.BlogCategory.Id, + Title = c.BlogCategory.Title, + Slug = c.BlogCategory.Slug + }).ToList() + }) + .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 + }; + + return new GetAllBlogPostsResponseDto { MetaData = metaData, Models = posts }; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetAllBlogPosts/GetAllBlogPostsResponseDto.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetAllBlogPosts/GetAllBlogPostsResponseDto.cs new file mode 100644 index 0000000..f876234 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetAllBlogPosts/GetAllBlogPostsResponseDto.cs @@ -0,0 +1,26 @@ +using CMSMicroservice.Application.Common.Models; +using CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPost; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetAllBlogPosts; + +public class GetAllBlogPostsResponseDto +{ + public MetaData MetaData { get; set; } = new(); + public List Models { get; set; } = new(); +} + +public class BlogPostListItemDto +{ + public long Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Slug { get; set; } = string.Empty; + public string? Summary { get; set; } + public string? FeaturedImageThumbnailPath { get; set; } + public int Status { get; set; } + public string StatusName { get; set; } = string.Empty; + public DateTime? PublishedAt { get; set; } + public int ViewCount { get; set; } + public bool IsFeatured { get; set; } + public DateTime Created { get; set; } + public List Categories { get; set; } = new(); +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPost/BlogPostDto.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPost/BlogPostDto.cs new file mode 100644 index 0000000..ae08e01 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPost/BlogPostDto.cs @@ -0,0 +1,39 @@ +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPost; + +public class BlogPostDto +{ + public long Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Slug { get; set; } = string.Empty; + public string? Summary { get; set; } + public string HtmlContent { get; set; } = string.Empty; + public string? FeaturedImagePath { get; set; } + public string? FeaturedImageThumbnailPath { get; set; } + public BlogPostStatus Status { get; set; } + public string StatusName { get; set; } = string.Empty; + public DateTime? PublishedAt { get; set; } + public int ViewCount { get; set; } + public long AuthorUserId { get; set; } + public bool IsFeatured { get; set; } + public int SortOrder { get; set; } + public DateTime Created { get; set; } + public DateTime? LastModified { get; set; } + public List Categories { get; set; } = new(); + public List Tags { get; set; } = new(); +} + +public class BlogPostCategoryDto +{ + public long Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Slug { get; set; } = string.Empty; +} + +public class BlogPostTagDto +{ + public long Id { get; set; } + public string Title { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPost/GetBlogPostQuery.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPost/GetBlogPostQuery.cs new file mode 100644 index 0000000..21d01a9 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPost/GetBlogPostQuery.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPost; + +public class GetBlogPostQuery : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPost/GetBlogPostQueryHandler.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPost/GetBlogPostQueryHandler.cs new file mode 100644 index 0000000..7443984 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPost/GetBlogPostQueryHandler.cs @@ -0,0 +1,66 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Enums; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPost; + +public class GetBlogPostQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetBlogPostQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetBlogPostQuery request, CancellationToken cancellationToken) + { + var post = await _context.BlogPosts + .Include(x => x.BlogPostCategories).ThenInclude(x => x.BlogCategory) + .Include(x => x.BlogPostTags).ThenInclude(x => x.Tag) + .FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken) + ?? throw new KeyNotFoundException($"مقاله با شناسه {request.Id} یافت نشد"); + + return new BlogPostDto + { + Id = post.Id, + Title = post.Title, + Slug = post.Slug, + Summary = post.Summary, + HtmlContent = post.HtmlContent, + FeaturedImagePath = post.FeaturedImagePath, + FeaturedImageThumbnailPath = post.FeaturedImageThumbnailPath, + Status = post.Status, + StatusName = GetStatusName(post.Status), + PublishedAt = post.PublishedAt, + ViewCount = post.ViewCount, + AuthorUserId = post.AuthorUserId, + IsFeatured = post.IsFeatured, + SortOrder = post.SortOrder, + Created = post.Created, + LastModified = post.LastModified, + Categories = post.BlogPostCategories.Select(c => new BlogPostCategoryDto + { + Id = c.BlogCategory.Id, + Title = c.BlogCategory.Title, + Slug = c.BlogCategory.Slug + }).ToList(), + Tags = post.BlogPostTags.Select(t => new BlogPostTagDto + { + Id = t.Tag.Id, + Title = t.Tag.Title, + Name = t.Tag.Name + }).ToList() + }; + } + + public static string GetStatusName(BlogPostStatus status) => status switch + { + BlogPostStatus.Draft => "پیش‌نویس", + BlogPostStatus.Published => "منتشرشده", + BlogPostStatus.Scheduled => "زمانبندی‌شده", + BlogPostStatus.Archived => "آرشیو", + _ => "نامشخص" + }; +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPostBySlug/GetBlogPostBySlugQuery.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPostBySlug/GetBlogPostBySlugQuery.cs new file mode 100644 index 0000000..860f737 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPostBySlug/GetBlogPostBySlugQuery.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPostBySlug; + +public class GetBlogPostBySlugQuery : IRequest +{ + public string Slug { get; set; } = string.Empty; +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPostBySlug/GetBlogPostBySlugQueryHandler.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPostBySlug/GetBlogPostBySlugQueryHandler.cs new file mode 100644 index 0000000..8c020b5 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetBlogPostBySlug/GetBlogPostBySlugQueryHandler.cs @@ -0,0 +1,58 @@ +using CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPost; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Enums; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPostBySlug; + +public class GetBlogPostBySlugQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetBlogPostBySlugQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetBlogPostBySlugQuery request, CancellationToken cancellationToken) + { + var post = await _context.BlogPosts + .Include(x => x.BlogPostCategories).ThenInclude(x => x.BlogCategory) + .Include(x => x.BlogPostTags).ThenInclude(x => x.Tag) + .FirstOrDefaultAsync(x => x.Slug == request.Slug && !x.IsDeleted, cancellationToken) + ?? throw new KeyNotFoundException($"مقاله با نشانی '{request.Slug}' یافت نشد"); + + return new BlogPostDto + { + Id = post.Id, + Title = post.Title, + Slug = post.Slug, + Summary = post.Summary, + HtmlContent = post.HtmlContent, + FeaturedImagePath = post.FeaturedImagePath, + FeaturedImageThumbnailPath = post.FeaturedImageThumbnailPath, + Status = post.Status, + StatusName = GetBlogPostQueryHandler.GetStatusName(post.Status), + PublishedAt = post.PublishedAt, + ViewCount = post.ViewCount, + AuthorUserId = post.AuthorUserId, + IsFeatured = post.IsFeatured, + SortOrder = post.SortOrder, + Created = post.Created, + LastModified = post.LastModified, + Categories = post.BlogPostCategories.Select(c => new BlogPostCategoryDto + { + Id = c.BlogCategory.Id, + Title = c.BlogCategory.Title, + Slug = c.BlogCategory.Slug + }).ToList(), + Tags = post.BlogPostTags.Select(t => new BlogPostTagDto + { + Id = t.Tag.Id, + Title = t.Tag.Title, + Name = t.Tag.Name + }).ToList() + }; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetFeaturedBlogPosts/GetFeaturedBlogPostsQuery.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetFeaturedBlogPosts/GetFeaturedBlogPostsQuery.cs new file mode 100644 index 0000000..3c6e701 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetFeaturedBlogPosts/GetFeaturedBlogPostsQuery.cs @@ -0,0 +1,55 @@ +using CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPost; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Enums; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetFeaturedBlogPosts; + +public class GetFeaturedBlogPostsQuery : IRequest> +{ + public int Count { get; set; } = 5; +} + +public class GetFeaturedBlogPostsQueryHandler : IRequestHandler> +{ + private readonly IApplicationDbContext _context; + + public GetFeaturedBlogPostsQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task> Handle(GetFeaturedBlogPostsQuery request, CancellationToken cancellationToken) + { + var posts = await _context.BlogPosts + .Include(x => x.BlogPostCategories).ThenInclude(x => x.BlogCategory) + .Where(x => !x.IsDeleted && x.Status == BlogPostStatus.Published && x.IsFeatured) + .OrderBy(x => x.SortOrder) + .ThenByDescending(x => x.PublishedAt) + .Take(request.Count) + .Select(x => new GetAllBlogPosts.BlogPostListItemDto + { + Id = x.Id, + Title = x.Title, + Slug = x.Slug, + Summary = x.Summary, + FeaturedImageThumbnailPath = x.FeaturedImageThumbnailPath, + Status = (int)x.Status, + StatusName = GetBlogPostQueryHandler.GetStatusName(x.Status), + PublishedAt = x.PublishedAt, + ViewCount = x.ViewCount, + IsFeatured = x.IsFeatured, + Created = x.Created, + Categories = x.BlogPostCategories.Select(c => new BlogPostCategoryDto + { + Id = c.BlogCategory.Id, + Title = c.BlogCategory.Title, + Slug = c.BlogCategory.Slug + }).ToList() + }) + .ToListAsync(cancellationToken); + + return posts; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetPublishedBlogPosts/GetPublishedBlogPostsQuery.cs b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetPublishedBlogPosts/GetPublishedBlogPostsQuery.cs new file mode 100644 index 0000000..8825b2b --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostCQ/Queries/GetPublishedBlogPosts/GetPublishedBlogPostsQuery.cs @@ -0,0 +1,81 @@ +using CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPost; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.Common.Models; +using CMSMicroservice.Domain.Enums; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogPostCQ.Queries.GetPublishedBlogPosts; + +public class GetPublishedBlogPostsQuery : IRequest +{ + public int PageNumber { get; set; } = 1; + public int PageSize { get; set; } = 10; + public string? SearchTerm { get; set; } + public long? CategoryId { get; set; } +} + +public class GetPublishedBlogPostsQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetPublishedBlogPostsQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetPublishedBlogPostsQuery request, CancellationToken cancellationToken) + { + var query = _context.BlogPosts + .Include(x => x.BlogPostCategories).ThenInclude(x => x.BlogCategory) + .Where(x => !x.IsDeleted && x.Status == BlogPostStatus.Published); + + if (request.CategoryId.HasValue) + query = query.Where(x => x.BlogPostCategories.Any(c => c.BlogCategoryId == request.CategoryId.Value)); + if (!string.IsNullOrEmpty(request.SearchTerm)) + { + var term = request.SearchTerm.ToLower(); + query = query.Where(x => x.Title.ToLower().Contains(term) || (x.Summary != null && x.Summary.ToLower().Contains(term))); + } + + var totalCount = await query.CountAsync(cancellationToken); + + var posts = await query + .OrderByDescending(x => x.PublishedAt) + .Skip((request.PageNumber - 1) * request.PageSize) + .Take(request.PageSize) + .Select(x => new GetAllBlogPosts.BlogPostListItemDto + { + Id = x.Id, + Title = x.Title, + Slug = x.Slug, + Summary = x.Summary, + FeaturedImageThumbnailPath = x.FeaturedImageThumbnailPath, + Status = (int)x.Status, + StatusName = GetBlogPostQueryHandler.GetStatusName(x.Status), + PublishedAt = x.PublishedAt, + ViewCount = x.ViewCount, + IsFeatured = x.IsFeatured, + Created = x.Created, + Categories = x.BlogPostCategories.Select(c => new BlogPostCategoryDto + { + Id = c.BlogCategory.Id, + Title = c.BlogCategory.Title, + Slug = c.BlogCategory.Slug + }).ToList() + }) + .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 + }; + + return new GetAllBlogPosts.GetAllBlogPostsResponseDto { MetaData = metaData, Models = posts }; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/AddBlogPostImage/AddBlogPostImageCommand.cs b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/AddBlogPostImage/AddBlogPostImageCommand.cs new file mode 100644 index 0000000..8a14516 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/AddBlogPostImage/AddBlogPostImageCommand.cs @@ -0,0 +1,13 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostImageCQ.Commands.AddBlogPostImage; + +public class AddBlogPostImageCommand : IRequest +{ + public long BlogPostId { get; set; } + public string ImagePath { get; set; } = default!; + public string ThumbnailPath { get; set; } = default!; + public string? AltText { get; set; } + public string? Caption { get; set; } + public int SortOrder { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/AddBlogPostImage/AddBlogPostImageCommandHandler.cs b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/AddBlogPostImage/AddBlogPostImageCommandHandler.cs new file mode 100644 index 0000000..9b929c0 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/AddBlogPostImage/AddBlogPostImageCommandHandler.cs @@ -0,0 +1,39 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Blog; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogPostImageCQ.Commands.AddBlogPostImage; + +public class AddBlogPostImageCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public AddBlogPostImageCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(AddBlogPostImageCommand request, CancellationToken cancellationToken) + { + var blogPost = await _context.BlogPosts.FirstOrDefaultAsync(x => x.Id == request.BlogPostId && !x.IsDeleted, cancellationToken); + if (blogPost == null) + throw new NotFoundException(nameof(BlogPost), request.BlogPostId); + + var entity = new BlogPostImage + { + BlogPostId = request.BlogPostId, + ImagePath = request.ImagePath, + ThumbnailPath = request.ThumbnailPath, + AltText = request.AltText, + Caption = request.Caption, + SortOrder = request.SortOrder + }; + + _context.BlogPostImages.Add(entity); + await _context.SaveChangesAsync(cancellationToken); + + return entity.Id; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/AddBlogPostImage/AddBlogPostImageCommandValidator.cs b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/AddBlogPostImage/AddBlogPostImageCommandValidator.cs new file mode 100644 index 0000000..911cfe1 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/AddBlogPostImage/AddBlogPostImageCommandValidator.cs @@ -0,0 +1,21 @@ +using FluentValidation; + +namespace CMSMicroservice.Application.BlogPostImageCQ.Commands.AddBlogPostImage; + +public class AddBlogPostImageCommandValidator : AbstractValidator +{ + public AddBlogPostImageCommandValidator() + { + RuleFor(x => x.BlogPostId) + .GreaterThan(0).WithMessage("شناسه پست نامعتبر است"); + + RuleFor(x => x.ImagePath) + .NotEmpty().WithMessage("مسیر تصویر الزامی است"); + + RuleFor(x => x.AltText) + .MaximumLength(500).WithMessage("متن جایگزین حداکثر ۵۰۰ کاراکتر"); + + RuleFor(x => x.Caption) + .MaximumLength(1000).WithMessage("عنوان تصویر حداکثر ۱۰۰۰ کاراکتر"); + } +} diff --git a/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/DeleteBlogPostImage/DeleteBlogPostImageCommand.cs b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/DeleteBlogPostImage/DeleteBlogPostImageCommand.cs new file mode 100644 index 0000000..0990205 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/DeleteBlogPostImage/DeleteBlogPostImageCommand.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostImageCQ.Commands.DeleteBlogPostImage; + +public class DeleteBlogPostImageCommand : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/DeleteBlogPostImage/DeleteBlogPostImageCommandHandler.cs b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/DeleteBlogPostImage/DeleteBlogPostImageCommandHandler.cs new file mode 100644 index 0000000..012c6f0 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/DeleteBlogPostImage/DeleteBlogPostImageCommandHandler.cs @@ -0,0 +1,29 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Blog; +using MediatR; + +namespace CMSMicroservice.Application.BlogPostImageCQ.Commands.DeleteBlogPostImage; + +public class DeleteBlogPostImageCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public DeleteBlogPostImageCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(DeleteBlogPostImageCommand request, CancellationToken cancellationToken) + { + var entity = await _context.BlogPostImages.FindAsync(new object[] { request.Id }, cancellationToken); + if (entity == null || entity.IsDeleted) + throw new NotFoundException(nameof(BlogPostImage), request.Id); + + entity.IsDeleted = true; + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/ReorderBlogPostImages/ReorderBlogPostImagesCommand.cs b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/ReorderBlogPostImages/ReorderBlogPostImagesCommand.cs new file mode 100644 index 0000000..852bcc1 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/ReorderBlogPostImages/ReorderBlogPostImagesCommand.cs @@ -0,0 +1,14 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostImageCQ.Commands.ReorderBlogPostImages; + +public class ReorderBlogPostImagesCommand : IRequest +{ + public List Items { get; set; } = new(); +} + +public class ImageSortItem +{ + public long Id { get; set; } + public int SortOrder { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/ReorderBlogPostImages/ReorderBlogPostImagesCommandHandler.cs b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/ReorderBlogPostImages/ReorderBlogPostImagesCommandHandler.cs new file mode 100644 index 0000000..282a9d5 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostImageCQ/Commands/ReorderBlogPostImages/ReorderBlogPostImagesCommandHandler.cs @@ -0,0 +1,34 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogPostImageCQ.Commands.ReorderBlogPostImages; + +public class ReorderBlogPostImagesCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public ReorderBlogPostImagesCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(ReorderBlogPostImagesCommand request, CancellationToken cancellationToken) + { + var ids = request.Items.Select(x => x.Id).ToList(); + var images = await _context.BlogPostImages + .Where(x => ids.Contains(x.Id) && !x.IsDeleted) + .ToListAsync(cancellationToken); + + foreach (var item in request.Items) + { + var image = images.FirstOrDefault(x => x.Id == item.Id); + if (image != null) + image.SortOrder = item.SortOrder; + } + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/BlogPostImageCQ/Queries/GetBlogPostImages/GetBlogPostImagesQuery.cs b/src/CMSMicroservice.Application/BlogPostImageCQ/Queries/GetBlogPostImages/GetBlogPostImagesQuery.cs new file mode 100644 index 0000000..8fd7e19 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostImageCQ/Queries/GetBlogPostImages/GetBlogPostImagesQuery.cs @@ -0,0 +1,19 @@ +using MediatR; + +namespace CMSMicroservice.Application.BlogPostImageCQ.Queries.GetBlogPostImages; + +public class GetBlogPostImagesQuery : IRequest> +{ + public long BlogPostId { get; set; } +} + +public class BlogPostImageDto +{ + public long Id { get; set; } + public long BlogPostId { get; set; } + public string ImagePath { get; set; } = default!; + public string? ThumbnailPath { get; set; } + public string? AltText { get; set; } + public string? Caption { get; set; } + public int SortOrder { get; set; } +} diff --git a/src/CMSMicroservice.Application/BlogPostImageCQ/Queries/GetBlogPostImages/GetBlogPostImagesQueryHandler.cs b/src/CMSMicroservice.Application/BlogPostImageCQ/Queries/GetBlogPostImages/GetBlogPostImagesQueryHandler.cs new file mode 100644 index 0000000..ecc3101 --- /dev/null +++ b/src/CMSMicroservice.Application/BlogPostImageCQ/Queries/GetBlogPostImages/GetBlogPostImagesQueryHandler.cs @@ -0,0 +1,35 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.BlogPostImageCQ.Queries.GetBlogPostImages; + +public class GetBlogPostImagesQueryHandler : IRequestHandler> +{ + private readonly IApplicationDbContext _context; + + public GetBlogPostImagesQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task> Handle(GetBlogPostImagesQuery request, CancellationToken cancellationToken) + { + var images = await _context.BlogPostImages + .Where(x => x.BlogPostId == request.BlogPostId && !x.IsDeleted) + .OrderBy(x => x.SortOrder) + .Select(x => new BlogPostImageDto + { + Id = x.Id, + BlogPostId = x.BlogPostId, + ImagePath = x.ImagePath, + ThumbnailPath = x.ThumbnailPath, + AltText = x.AltText, + Caption = x.Caption, + SortOrder = x.SortOrder + }) + .ToListAsync(cancellationToken); + + return images; + } +} diff --git a/src/CMSMicroservice.Application/CategoryCQ/Queries/GetAllCategoryByFilter/GetAllCategoryByFilterQueryHandler.cs b/src/CMSMicroservice.Application/CategoryCQ/Queries/GetAllCategoryByFilter/GetAllCategoryByFilterQueryHandler.cs index 3d30861..83d0fd3 100644 --- a/src/CMSMicroservice.Application/CategoryCQ/Queries/GetAllCategoryByFilter/GetAllCategoryByFilterQueryHandler.cs +++ b/src/CMSMicroservice.Application/CategoryCQ/Queries/GetAllCategoryByFilter/GetAllCategoryByFilterQueryHandler.cs @@ -31,7 +31,18 @@ public class GetAllCategoryByFilterQueryHandler : IRequestHandler().ToListAsync(cancellationToken) + .Select(x => new GetAllCategoryByFilterResponseModel + { + Id = x.Id, + Name = x.Name, + Title = x.Title, + Description = x.Description, + ImagePath = x.ImagePath, + ParentId = x.ParentId, + IsActive = x.IsActive, + SortOrder = x.SortOrder, + ProductCount = x.ProductCategories.Count + }).ToListAsync(cancellationToken) }; } } diff --git a/src/CMSMicroservice.Application/CategoryCQ/Queries/GetAllCategoryByFilter/GetAllCategoryByFilterResponseDto.cs b/src/CMSMicroservice.Application/CategoryCQ/Queries/GetAllCategoryByFilter/GetAllCategoryByFilterResponseDto.cs index 4edaf18..7fd92b2 100644 --- a/src/CMSMicroservice.Application/CategoryCQ/Queries/GetAllCategoryByFilter/GetAllCategoryByFilterResponseDto.cs +++ b/src/CMSMicroservice.Application/CategoryCQ/Queries/GetAllCategoryByFilter/GetAllCategoryByFilterResponseDto.cs @@ -24,4 +24,6 @@ public class GetAllCategoryByFilterResponseDto public bool IsActive { get; set; } //ترتیب نمایش public int SortOrder { get; set; } + //تعداد محصولات + public int ProductCount { get; set; } } diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandHandler.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandHandler.cs index 07821fd..b135fa0 100644 --- a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandHandler.cs +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandHandler.cs @@ -53,20 +53,28 @@ public class AcceptClubMembershipContractCommandHandler AcceptClubMembershipContractCommand request, CancellationToken cancellationToken) { + // خواندن UserId از JWT (امن‌تر از دریافت از کلاینت) + if (!long.TryParse(_currentUser.UserId, out var userId)) + return new AcceptClubMembershipContractResponseDto + { + Success = false, + Message = "کاربر احراز هویت نشده است" + }; + _logger.LogInformation( "Processing club membership contract for UserId: {UserId}", - request.UserId + userId ); // 1. دریافت کاربر var user = await _context.Users .Include(u => u.ClubMembership) - .FirstOrDefaultAsync(u => u.Id == request.UserId, cancellationToken); + .FirstOrDefaultAsync(u => u.Id == userId, cancellationToken); if (user == null) { - _logger.LogWarning("User not found: {UserId}", request.UserId); - throw new NotFoundException(nameof(User), request.UserId); + _logger.LogWarning("User not found: {UserId}", userId); + throw new NotFoundException(nameof(User), userId); } // 2. بررسی خرید پکیج diff --git a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandValidator.cs b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandValidator.cs index 8c5f308..ba87684 100644 --- a/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandValidator.cs +++ b/src/CMSMicroservice.Application/ClubMembershipCQ/Commands/AcceptClubMembershipContract/AcceptClubMembershipContractCommandValidator.cs @@ -4,10 +4,6 @@ public class AcceptClubMembershipContractCommandValidator : AbstractValidator x.UserId) - .GreaterThan(0) - .WithMessage("شناسه کاربر الزامی است"); - RuleFor(x => x.OtpCode) .NotEmpty() .WithMessage("کد تایید الزامی است") diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQuery.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQuery.cs index b122c12..4d27587 100644 --- a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQuery.cs +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQuery.cs @@ -6,14 +6,14 @@ namespace CMSMicroservice.Application.CommissionCQ.Queries.GetAllWeeklyPools; public record GetAllWeeklyPoolsQuery : IRequest { /// - /// از هفته (فیلتر اختیاری) + /// از هفته — WeekDefinitionId (فیلتر اختیاری) /// - public int? FromWeekOrder { get; init; } + public long? FromWeekDefinitionId { get; init; } /// - /// تا هفته (فیلتر اختیاری) + /// تا هفته — WeekDefinitionId (فیلتر اختیاری) /// - public int? ToWeekOrder { get; init; } + public long? ToWeekDefinitionId { get; init; } /// /// فقط Pool های محاسبه شده diff --git a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQueryHandler.cs b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQueryHandler.cs index efdbf9e..d49e89f 100644 --- a/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQueryHandler.cs +++ b/src/CMSMicroservice.Application/CommissionCQ/Queries/GetAllWeeklyPools/GetAllWeeklyPoolsQueryHandler.cs @@ -20,14 +20,14 @@ public class GetAllWeeklyPoolsQueryHandler : IRequestHandler x.WeekDefinition.WeekOrder>=request.FromWeekOrder ); + query = query.Where(x => x.WeekDefinitionId >= request.FromWeekDefinitionId); } - if (request.ToWeekOrder!=null) + if (request.ToWeekDefinitionId != null) { - query = query.Where(x =>x.WeekDefinition.WeekOrder<= request.ToWeekOrder); + query = query.Where(x => x.WeekDefinitionId <= request.ToWeekDefinitionId); } if (request.OnlyCalculated.HasValue && request.OnlyCalculated.Value) diff --git a/src/CMSMicroservice.Application/Common/Behaviours/LoggingBehaviour.cs b/src/CMSMicroservice.Application/Common/Behaviours/LoggingBehaviour.cs index 951bc51..836b657 100644 --- a/src/CMSMicroservice.Application/Common/Behaviours/LoggingBehaviour.cs +++ b/src/CMSMicroservice.Application/Common/Behaviours/LoggingBehaviour.cs @@ -18,7 +18,10 @@ public class LoggingBehaviour : IRequestPreProcessor where T { var requestName = typeof(TRequest).Name; var userId = _currentUserService.UserId ?? string.Empty; - _logger.LogInformation("Request: {Name} {@UserId} {@Request}", - requestName, userId, request); + var safeLog = request?.ToString() ?? ""; + if (safeLog.Length > 2000) + safeLog = safeLog[..2000] + "... [TRUNCATED]"; + _logger.LogInformation("Request: {Name} {UserId} {Request}", + requestName, userId, safeLog); } } diff --git a/src/CMSMicroservice.Application/Common/Behaviours/PerformanceBehaviour.cs b/src/CMSMicroservice.Application/Common/Behaviours/PerformanceBehaviour.cs index 45cd4e9..6d358ad 100644 --- a/src/CMSMicroservice.Application/Common/Behaviours/PerformanceBehaviour.cs +++ b/src/CMSMicroservice.Application/Common/Behaviours/PerformanceBehaviour.cs @@ -1,16 +1,20 @@ using System.Diagnostics; +using System.Text.RegularExpressions; using MediatR; using Microsoft.Extensions.Logging; namespace CMSMicroservice.Application.Common.Behaviours; -public class PerformanceBehaviour : IPipelineBehavior +public partial class PerformanceBehaviour : IPipelineBehavior where TRequest : IRequest { private readonly Stopwatch _timer; private readonly ILogger _logger; private readonly ICurrentUserService _currentUserService; + [GeneratedRegex(@"ImageFile(?:Bytes|Mime|FileName)|ImageFile|File", RegexOptions.None)] + private static partial Regex BinaryPropertyPattern(); + public PerformanceBehaviour(ILogger logger, ICurrentUserService currentUserService) { _timer = new Stopwatch(); @@ -33,11 +37,20 @@ public class PerformanceBehaviour : IPipelineBehavior 2000) + return text[..2000] + "... [TRUNCATED]"; + return text; + } } diff --git a/src/CMSMicroservice.Application/Common/Behaviours/UnhandledExceptionBehaviour.cs b/src/CMSMicroservice.Application/Common/Behaviours/UnhandledExceptionBehaviour.cs index 12648ad..80754f3 100644 --- a/src/CMSMicroservice.Application/Common/Behaviours/UnhandledExceptionBehaviour.cs +++ b/src/CMSMicroservice.Application/Common/Behaviours/UnhandledExceptionBehaviour.cs @@ -22,8 +22,11 @@ public class UnhandledExceptionBehaviour : IPipelineBehavio catch (Exception ex) { var requestName = typeof(TRequest).Name; + var safeLog = request?.ToString() ?? ""; + if (safeLog.Length > 2000) + safeLog = safeLog[..2000] + "... [TRUNCATED]"; - _logger.LogError(ex, "Request: Unhandled Exception for Request {Name} {@Request}", requestName, request); + _logger.LogError(ex, "Request: Unhandled Exception for Request {Name} {Request}", requestName, safeLog); throw; } diff --git a/src/CMSMicroservice.Application/Common/FileManager/IFileManager.cs b/src/CMSMicroservice.Application/Common/FileManager/IFileManager.cs new file mode 100644 index 0000000..e0afc00 --- /dev/null +++ b/src/CMSMicroservice.Application/Common/FileManager/IFileManager.cs @@ -0,0 +1,67 @@ +namespace CMSMicroservice.Application.Common.FileManager; + +/// +/// سرویس مدیریت فایل — ذخیره روی دیسک، مسیر نسبی در دیتابیس +/// موقع واکشی: خواندن از دیسک و تبدیل به base64 data-URI +/// +public interface IFileManager +{ + /// + /// آپلود یک فایل خام به دیسک + /// + /// نتیجه آپلود شامل مسیر نسبی فایل + /// در صورت خطای آپلود + Task UploadAsync( + string directory, + byte[] fileBytes, + string mime, + string? fileName = null, + CancellationToken ct = default); + + /// + /// آپلود تصویر با بهینه‌سازی خودکار + ساخت بندانگشتی + /// + /// نتیجه آپلود شامل تصویر اصلی و بندانگشتی (مسیرهای نسبی) + /// در صورت خطای آپلود + Task UploadImageAsync( + string directory, + byte[] fileBytes, + string mime, + string? fileName = null, + CancellationToken ct = default); + + /// + /// حذف فایل از دیسک + /// + Task DeleteAsync(long fileId, CancellationToken ct = default); + + /// + /// خواندن فایل از دیسک و تبدیل به base64 data-URI + /// اگر مسیر از قبل data: باشد، همان را برمی‌گرداند + /// اگر فایل وجود نداشته باشد، رشته خالی برمی‌گرداند + /// + string ResolveImageUrl(string? path); +} + +/// +/// نتیجه آپلود فایل +/// +/// شناسه فایل در FMS +/// مسیر فایل ذخیره‌شده (مثلاً /Images/Products/abc.jpg) +public sealed record UploadedFile(long Id, string Path); + +/// +/// نتیجه آپلود تصویر — شامل تصویر اصلی و بندانگشتی +/// +/// تصویر اصلی بهینه‌شده +/// تصویر بندانگشتی +public sealed record UploadedImage(UploadedFile Main, UploadedFile Thumbnail); + +/// +/// خطای آپلود فایل +/// +public class FileUploadException : Exception +{ + public FileUploadException(string message) : base(message) { } + public FileUploadException(string message, Exception inner) : base(message, inner) { } +} diff --git a/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs b/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs index 637481e..98dfc96 100644 --- a/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs +++ b/src/CMSMicroservice.Application/Common/Interfaces/IApplicationDbContext.cs @@ -1,3 +1,5 @@ +using CMSMicroservice.Domain.Entities.Blog; +using CMSMicroservice.Domain.Entities.Content; using CMSMicroservice.Domain.Entities.Payment; using CMSMicroservice.Domain.Entities.Order; using CMSMicroservice.Domain.Entities.DiscountShop; @@ -66,6 +68,17 @@ public interface IApplicationDbContext DbSet InventoryItems { get; } DbSet StockMovements { get; } + // ============= Blog ============= + DbSet BlogPosts { get; } + DbSet BlogCategories { get; } + DbSet BlogPostCategories { get; } + DbSet BlogPostTags { get; } + DbSet BlogPostImages { get; } + + // ============= Content Management ============= + DbSet SitePages { get; } + DbSet SitePageSections { get; } + /// /// دسترسی به DatabaseFacade برای اجرای raw SQL و Stored Procedures /// diff --git a/src/CMSMicroservice.Application/Common/Interfaces/IFileManagementService.cs b/src/CMSMicroservice.Application/Common/Interfaces/IFileManagementService.cs deleted file mode 100644 index 50b537c..0000000 --- a/src/CMSMicroservice.Application/Common/Interfaces/IFileManagementService.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace CMSMicroservice.Application.Common.Interfaces; - -/// -/// Service for uploading files to FMS (File Management Service) -/// -public interface IFileManagementService -{ - /// - /// Uploads a file to FMS and returns the stored file path - /// - /// Target directory path (e.g. "Images/Products") - /// Raw file bytes - /// MIME type (e.g. "image/jpeg") - /// Original file name - /// Cancellation token - /// The stored file path returned by FMS, or null if upload failed - Task UploadFileAsync(string directory, byte[] fileBytes, string mime, string? fileName, CancellationToken cancellationToken = default); - - /// - /// Uploads an image to FMS with optimization (resize + compress) - /// Returns both main image path and thumbnail path - /// - /// Target directory path (e.g. "Images/Products") - /// Raw image bytes - /// MIME type (e.g. "image/jpeg") - /// Original file name - /// Cancellation token - /// Tuple of (mainImagePath, thumbnailPath), either can be null if upload failed - Task<(string? MainImagePath, string? ThumbnailPath)> UploadImageWithThumbnailAsync( - string directory, byte[] fileBytes, string mime, string? fileName, - CancellationToken cancellationToken = default); - - /// - /// Deletes a file from FMS by its ID - /// - Task DeleteFileAsync(long fileId, CancellationToken cancellationToken = default); -} diff --git a/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs b/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs index 912cfce..5a816db 100644 --- a/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs +++ b/src/CMSMicroservice.Application/Common/Interfaces/IPaymentGatewayService.cs @@ -27,6 +27,24 @@ public interface IPaymentGatewayService string verificationToken, CancellationToken cancellationToken = default); + /// + /// تأیید پرداخت با مبلغ — برای درگاه‌هایی مثل زرین‌پال که مبلغ را در Verify نیاز دارند + /// + /// شماره مرجع تراکنش (Authority در زرین‌پال) + /// توکن تأیید از درگاه (Status در زرین‌پال) + /// مبلغ تراکنش به تومان + /// + /// وضعیت نهایی تراکنش + Task VerifyPaymentAsync( + string refId, + string verificationToken, + decimal amountInToman, + CancellationToken cancellationToken = default) + { + // پیش‌فرض: درگاه‌هایی که Amount نمی‌خواهند، از overload بدون amount استفاده کنند + return VerifyPaymentAsync(refId, verificationToken, cancellationToken); + } + /// /// واریز مبلغ به حساب کاربر (برداشت از کیف پول) /// diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/AddDiscountProductImage/AddDiscountProductImageCommand.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/AddDiscountProductImage/AddDiscountProductImageCommand.cs index 2888f40..4722810 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/AddDiscountProductImage/AddDiscountProductImageCommand.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/AddDiscountProductImage/AddDiscountProductImageCommand.cs @@ -9,4 +9,9 @@ public class AddDiscountProductImageCommand : IRequest public string ThumbnailPath { get; set; } = string.Empty; public string? Title { get; set; } public string? AltText { get; set; } + + // Image file upload + public byte[]? ImageFileBytes { get; set; } + public string? ImageFileMime { get; set; } + public string? ImageFileName { get; set; } } diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/AddDiscountProductImage/AddDiscountProductImageCommandHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/AddDiscountProductImage/AddDiscountProductImageCommandHandler.cs index 43dc131..a4db77e 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/AddDiscountProductImage/AddDiscountProductImageCommandHandler.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/AddDiscountProductImage/AddDiscountProductImageCommandHandler.cs @@ -1,3 +1,4 @@ +using CMSMicroservice.Application.Common.FileManager; using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Entities.DiscountShop; using MediatR; @@ -8,10 +9,12 @@ namespace CMSMicroservice.Application.DiscountShopCQ.Commands.AddDiscountProduct public class AddDiscountProductImageCommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; + private readonly IFileManager _fileManager; - public AddDiscountProductImageCommandHandler(IApplicationDbContext context) + public AddDiscountProductImageCommandHandler(IApplicationDbContext context, IFileManager fileManager) { _context = context; + _fileManager = fileManager; } public async Task Handle(AddDiscountProductImageCommand request, CancellationToken cancellationToken) @@ -23,6 +26,23 @@ public class AddDiscountProductImageCommandHandler : IRequestHandler 0 }) + { + var result = await _fileManager.UploadImageAsync( + "Images/DiscountProducts/Gallery", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + imagePath = result.Main.Path; + thumbnailPath = result.Thumbnail.Path; + } + // Get the max sort order for this product var maxSortOrder = await _context.DiscountProductImages .Where(i => i.DiscountProductId == request.DiscountProductId) @@ -31,8 +51,8 @@ public class AddDiscountProductImageCommandHandler : IRequestHandler public int MaxDiscountPercent { get; set; } public string ImagePath { get; set; } public string ThumbnailPath { get; set; } + public int SortOrder { get; set; } + public bool IsActive { get; set; } = true; public List CategoryIds { get; set; } = new(); + + // Image file upload + public byte[]? ImageFileBytes { get; set; } + public string? ImageFileMime { get; set; } + public string? ImageFileName { get; set; } + public byte[]? ThumbnailFileBytes { get; set; } + public string? ThumbnailFileMime { get; set; } + public string? ThumbnailFileName { get; set; } } diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandHandler.cs index 27ee7f8..b0a7d7b 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandHandler.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/CreateDiscountProduct/CreateDiscountProductCommandHandler.cs @@ -1,3 +1,4 @@ +using CMSMicroservice.Application.Common.FileManager; using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Entities.DiscountShop; using CMSMicroservice.Domain.Enums; @@ -10,13 +11,16 @@ public class CreateDiscountProductCommandHandler : IRequestHandler Handle(CreateDiscountProductCommand request, CancellationToken cancellationToken) @@ -28,15 +32,42 @@ public class CreateDiscountProductCommandHandler : IRequestHandler 0 }) + { + var result = await _fileManager.UploadImageAsync( + "Images/DiscountProducts", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + product.ImagePath = result.Main.Path; + product.ThumbnailPath = result.Thumbnail.Path; + } + + // آپلود بندانگشتی جداگانه (اختیاری) + if (request.ThumbnailFileBytes is { Length: > 0 }) + { + var thumbResult = await _fileManager.UploadAsync( + "Images/DiscountProducts/Thumbnails", + request.ThumbnailFileBytes, + request.ThumbnailFileMime ?? "image/jpeg", + request.ThumbnailFileName, + cancellationToken); + + product.ThumbnailPath = thumbResult.Path; + } + _context.DiscountProducts.Add(product); await _context.SaveChangesAsync(cancellationToken); diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommand.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommand.cs index b32b074..1f20f3c 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommand.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommand.cs @@ -18,4 +18,9 @@ public class PlaceOrderResponseDto public long TotalAmount { get; set; } public long DiscountBalanceUsed { get; set; } public long GatewayAmountRequired { get; set; } + + /// + /// URL درگاه پرداخت — اگر null باشد یعنی نیاز به پرداخت آنلاین نیست + /// + public string? PaymentUrl { get; set; } } diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs index 5513f1c..e866e82 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/PlaceOrder/PlaceOrderCommandHandler.cs @@ -5,6 +5,8 @@ using CMSMicroservice.Domain.Entities.Payment; using CMSMicroservice.Domain.Enums; using MediatR; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; namespace CMSMicroservice.Application.DiscountShopCQ.Commands.PlaceOrder; @@ -12,13 +14,22 @@ public class PlaceOrderCommandHandler : IRequestHandler _logger; public PlaceOrderCommandHandler( IApplicationDbContext context, - IInventoryService inventoryService) + IInventoryService inventoryService, + IPaymentGatewayService paymentGateway, + IConfiguration configuration, + ILogger logger) { _context = context; _inventoryService = inventoryService; + _paymentGateway = paymentGateway; + _configuration = configuration; + _logger = logger; } public async Task Handle(PlaceOrderCommand request, CancellationToken cancellationToken) @@ -172,15 +183,102 @@ public class PlaceOrderCommandHandler : IRequestHandler ۰ باشد، باید به درگاه پرداخت متصل شویم + string? paymentUrl = null; + + if (finalGatewayAmount > 0) + { + try + { + // آدرس callback — زرین‌پال بعد از پرداخت کاربر را به اینجا هدایت می‌کند + var cmsBaseUrl = _configuration["CmsBaseUrl"] ?? "https://localhost:32846"; + var callbackUrl = $"{cmsBaseUrl}/api/payment/discount-order/callback?orderId={order.Id}"; + + // درخواست به درگاه + var paymentResult = await _paymentGateway.InitiatePaymentAsync(new PaymentRequest + { + Amount = finalGatewayAmount, + UserId = request.UserId, + Description = $"فروشگاه تخفیفی - سفارش #{order.Id}", + CallbackUrl = callbackUrl + }, cancellationToken); + + if (paymentResult.IsSuccess && !string.IsNullOrEmpty(paymentResult.GatewayUrl)) + { + // ذخیره Authority/RefId در تراکنش برای verify بعدی + transaction.RefId = paymentResult.RefId; + await _context.SaveChangesAsync(cancellationToken); + + paymentUrl = paymentResult.GatewayUrl; + _logger.LogInformation( + "Payment gateway initiated for DiscountOrder #{OrderId}: RefId={RefId}, Url={Url}", + order.Id, paymentResult.RefId, paymentResult.GatewayUrl); + } + else + { + _logger.LogError( + "Payment gateway initiation failed for DiscountOrder #{OrderId}: {Error}", + order.Id, paymentResult.ErrorMessage); + + return new PlaceOrderResponseDto + { + Success = false, + Message = $"خطا در اتصال به درگاه پرداخت: {paymentResult.ErrorMessage}", + OrderId = order.Id + }; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Payment gateway exception for DiscountOrder #{OrderId}", order.Id); + return new PlaceOrderResponseDto + { + Success = false, + Message = $"خطا در اتصال به درگاه پرداخت: {ex.Message}", + OrderId = order.Id + }; + } + } + else + { + // اگر کل مبلغ از کیف تخفیفی پرداخت شد — مستقیماً تکمیل شود + transaction.PaymentStatus = PaymentStatus.Success; + transaction.PaymentDate = DateTime.Now; + order.PaymentStatus = PaymentStatus.Success; + order.PaymentDate = DateTime.Now; + order.DeliveryStatus = DeliveryStatus.InTransit; + + var walletForDeduct = await _context.UserWallets + .FirstOrDefaultAsync(w => w.UserId == request.UserId, cancellationToken); + if (walletForDeduct != null) + walletForDeduct.DiscountBalance -= actualDiscountBalanceUsed; + + foreach (var cartItem in cartItems) + { + await _inventoryService.ConfirmSaleAsync( + cartItem.ProductId, ProductType.DiscountProduct, + cartItem.Count, order.Id, cancellationToken); + cartItem.Product.SaleCount += cartItem.Count; + } + + await _context.SaveChangesAsync(cancellationToken); + _logger.LogInformation( + "DiscountOrder #{OrderId} fully paid via discount balance ({Amount} T)", + order.Id, actualDiscountBalanceUsed); + } + return new PlaceOrderResponseDto { Success = true, - Message = "سفارش ایجاد شد. لطفا پرداخت را تکمیل کنید", + Message = finalGatewayAmount > 0 + ? "سفارش ایجاد شد. در حال انتقال به درگاه پرداخت..." + : "سفارش با موفقیت ثبت و پرداخت شد", OrderId = order.Id, TransactionId = transaction.Id, TotalAmount = totalAmount, DiscountBalanceUsed = actualDiscountBalanceUsed, - GatewayAmountRequired = finalGatewayAmount + GatewayAmountRequired = finalGatewayAmount, + PaymentUrl = paymentUrl }; } } diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommand.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommand.cs index a071314..80bd1e0 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommand.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommand.cs @@ -12,7 +12,16 @@ public class UpdateDiscountProductCommand : IRequest public int MaxDiscountPercent { get; set; } public string ImagePath { get; set; } public string ThumbnailPath { get; set; } + public int SortOrder { get; set; } public int RemainingCount { get; set; } public bool IsActive { get; set; } public List CategoryIds { get; set; } = new(); + + // Image file upload + public byte[]? ImageFileBytes { get; set; } + public string? ImageFileMime { get; set; } + public string? ImageFileName { get; set; } + public byte[]? ThumbnailFileBytes { get; set; } + public string? ThumbnailFileMime { get; set; } + public string? ThumbnailFileName { get; set; } } diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandHandler.cs index 19252ad..426913b 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandHandler.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Commands/UpdateDiscountProduct/UpdateDiscountProductCommandHandler.cs @@ -1,3 +1,4 @@ +using CMSMicroservice.Application.Common.FileManager; using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Entities.DiscountShop; using MediatR; @@ -8,10 +9,12 @@ namespace CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateDiscountProd public class UpdateDiscountProductCommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; + private readonly IFileManager _fileManager; - public UpdateDiscountProductCommandHandler(IApplicationDbContext context) + public UpdateDiscountProductCommandHandler(IApplicationDbContext context, IFileManager fileManager) { _context = context; + _fileManager = fileManager; } public async Task Handle(UpdateDiscountProductCommand request, CancellationToken cancellationToken) @@ -27,11 +30,44 @@ public class UpdateDiscountProductCommandHandler : IRequestHandler 0 }) + { + var result = await _fileManager.UploadImageAsync( + "Images/DiscountProducts", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + product.ImagePath = result.Main.Path; + product.ThumbnailPath = result.Thumbnail.Path; + } + else if (!string.IsNullOrEmpty(request.ImagePath)) + { + product.ImagePath = request.ImagePath; + } + + // آپلود بندانگشتی جداگانه (اختیاری) + if (request.ThumbnailFileBytes is { Length: > 0 }) + { + var thumbResult = await _fileManager.UploadAsync( + "Images/DiscountProducts/Thumbnails", + request.ThumbnailFileBytes, + request.ThumbnailFileMime ?? "image/jpeg", + request.ThumbnailFileName, + cancellationToken); + + product.ThumbnailPath = thumbResult.Path; + } + else if (!string.IsNullOrEmpty(request.ThumbnailPath)) + { + product.ThumbnailPath = request.ThumbnailPath; + } + // Update categories var existingCategories = await _context.DiscountProductCategories .Where(pc => pc.ProductId == request.ProductId) diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetOrderById/GetOrderByIdQuery.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetOrderById/GetOrderByIdQuery.cs index ead7df8..46e1e86 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetOrderById/GetOrderByIdQuery.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetOrderById/GetOrderByIdQuery.cs @@ -43,4 +43,6 @@ public class OrderItemDto public int DiscountPercentUsed { get; set; } public long DiscountAmount { get; set; } public long FinalPrice { get; set; } + public string ImagePath { get; set; } + public string ThumbnailPath { get; set; } } diff --git a/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetOrderById/GetOrderByIdQueryHandler.cs b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetOrderById/GetOrderByIdQueryHandler.cs index a5e7292..1e55328 100644 --- a/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetOrderById/GetOrderByIdQueryHandler.cs +++ b/src/CMSMicroservice.Application/DiscountShopCQ/Queries/GetOrderById/GetOrderByIdQueryHandler.cs @@ -53,7 +53,9 @@ public class GetOrderByIdQueryHandler : IRequestHandler public string Mobile { get; init; } //مقصود public string Purpose { get; init; } - + /// + /// شناسه GUID قرارداد (فقط برای purpose=signcontract/signclubcontract) + /// + public string? SignGuid { get; init; } } \ No newline at end of file diff --git a/src/CMSMicroservice.Application/OtpTokenCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommandHandler.cs b/src/CMSMicroservice.Application/OtpTokenCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommandHandler.cs index 8cf3ff1..745790b 100644 --- a/src/CMSMicroservice.Application/OtpTokenCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommandHandler.cs +++ b/src/CMSMicroservice.Application/OtpTokenCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommandHandler.cs @@ -57,7 +57,7 @@ public class CreateNewOtpTokenCommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; - private readonly IFileManagementService _fileManagementService; + private readonly IFileManager _fileManager; private readonly ILogger _logger; public AddProductImageCommandHandler( IApplicationDbContext context, - IFileManagementService fileManagementService, + IFileManager fileManager, ILogger logger) { _context = context; - _fileManagementService = fileManagementService; + _fileManager = fileManager; _logger = logger; } public async Task Handle(AddProductImageCommand request, CancellationToken cancellationToken) { - // Verify product exists + // بررسی وجود محصول var productExists = await _context.Products .AnyAsync(p => p.Id == request.ProductId, cancellationToken); if (!productExists) throw new NotFoundException(nameof(Product), request.ProductId); - string imagePath = string.Empty; - string thumbnailPath = string.Empty; + // بدون فایل تصویر، کاری انجام نمی‌شود + if (request.ImageFileBytes is not { Length: > 0 }) + throw new FileUploadException("فایل تصویر ارسال نشده است"); - // Upload image to FMS - if (request.ImageFileBytes is { Length: > 0 }) - { - try - { - var (mainPath, thumbPath) = await _fileManagementService.UploadImageWithThumbnailAsync( - "Images/Products/Gallery", - request.ImageFileBytes, - request.ImageFileMime ?? "image/jpeg", - request.ImageFileName, - cancellationToken); + // آپلود تصویر به FMS (اگر خطا بخوره، exception پرتاب می‌شه و entity ذخیره نمیشه) + var uploaded = await _fileManager.UploadImageAsync( + "Images/Products/Gallery", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); - imagePath = mainPath ?? string.Empty; - thumbnailPath = thumbPath ?? string.Empty; - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to upload gallery image to FMS for product {ProductId}", request.ProductId); - } - } - - // Create ProductImage entity + // ساخت رکورد تصویر var productImage = new ProductImage { Title = request.Title, - ImagePath = imagePath, - ImageThumbnailPath = thumbnailPath + ImagePath = uploaded.Main.Path, + ImageThumbnailPath = uploaded.Thumbnail.Path }; await _context.ProductImages.AddAsync(productImage, cancellationToken); await _context.SaveChangesAsync(cancellationToken); - // Create ProductGallery join entity + // اتصال تصویر به محصول var productGallery = new ProductGallery { ProductId = request.ProductId, diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsCommandHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsCommandHandler.cs index 6de0876..8bd4783 100644 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsCommandHandler.cs +++ b/src/CMSMicroservice.Application/ProductsCQ/Commands/CreateNewProducts/CreateNewProductsCommandHandler.cs @@ -1,3 +1,4 @@ +using CMSMicroservice.Application.Common.FileManager; using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Entities; using Microsoft.Extensions.Logging; @@ -7,16 +8,16 @@ namespace CMSMicroservice.Application.ProductsCQ.Commands.CreateNewProducts; public class CreateNewProductsCommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; - private readonly IFileManagementService _fileManagementService; + private readonly IFileManager _fileManager; private readonly ILogger _logger; public CreateNewProductsCommandHandler( IApplicationDbContext context, - IFileManagementService fileManagementService, + IFileManager fileManager, ILogger logger) { _context = context; - _fileManagementService = fileManagementService; + _fileManager = fileManager; _logger = logger; } @@ -32,56 +33,38 @@ public class CreateNewProductsCommandHandler : IRequestHandler 0 }) { - try - { - var (mainPath, thumbPath) = await _fileManagementService.UploadImageWithThumbnailAsync( - "Images/Products", - request.ImageFileBytes, - request.ImageFileMime ?? "image/jpeg", - request.ImageFileName, - cancellationToken); + var result = await _fileManager.UploadImageAsync( + "Images/Products", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); - if (!string.IsNullOrWhiteSpace(mainPath)) - entity.ImagePath = mainPath; - - if (!string.IsNullOrWhiteSpace(thumbPath)) - entity.ThumbnailPath = thumbPath; - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to upload product image to FMS"); - } + entity.ImagePath = result.Main.Path; + entity.ThumbnailPath = result.Thumbnail.Path; } - - // Handle separate thumbnail upload if provided (and not already set from main image) - if (request.ThumbnailFileBytes is { Length: > 0 } && string.IsNullOrWhiteSpace(entity.ThumbnailPath)) - { - try - { - var thumbPath = await _fileManagementService.UploadFileAsync( - "Images/Products/Thumbnails", - request.ThumbnailFileBytes, - request.ThumbnailFileMime ?? "image/jpeg", - request.ThumbnailFileName, - cancellationToken); - if (!string.IsNullOrWhiteSpace(thumbPath)) - entity.ThumbnailPath = thumbPath; - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to upload product thumbnail to FMS"); - } + // آپلود بندانگشتی جداگانه (اختیاری — جایگزین بندانگشتی خودکار) + if (request.ThumbnailFileBytes is { Length: > 0 }) + { + var thumbResult = await _fileManager.UploadAsync( + "Images/Products/Thumbnails", + request.ThumbnailFileBytes, + request.ThumbnailFileMime ?? "image/jpeg", + request.ThumbnailFileName, + cancellationToken); + + entity.ThumbnailPath = thumbResult.Path; } await _context.Products.AddAsync(entity, cancellationToken); diff --git a/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProducts/UpdateProductsCommandHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProducts/UpdateProductsCommandHandler.cs index 8a24baa..da72bfc 100644 --- a/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProducts/UpdateProductsCommandHandler.cs +++ b/src/CMSMicroservice.Application/ProductsCQ/Commands/UpdateProducts/UpdateProductsCommandHandler.cs @@ -1,3 +1,4 @@ +using CMSMicroservice.Application.Common.FileManager; using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Domain.Entities; using Microsoft.Extensions.Logging; @@ -7,16 +8,16 @@ namespace CMSMicroservice.Application.ProductsCQ.Commands.UpdateProducts; public class UpdateProductsCommandHandler : IRequestHandler { private readonly IApplicationDbContext _context; - private readonly IFileManagementService _fileManagementService; + private readonly IFileManager _fileManager; private readonly ILogger _logger; public UpdateProductsCommandHandler( IApplicationDbContext context, - IFileManagementService fileManagementService, + IFileManager fileManager, ILogger logger) { _context = context; - _fileManagementService = fileManagementService; + _fileManager = fileManager; _logger = logger; } @@ -38,57 +39,31 @@ public class UpdateProductsCommandHandler : IRequestHandler 0 }) { - try - { - var (mainPath, thumbPath) = await _fileManagementService.UploadImageWithThumbnailAsync( - "Images/Products", - request.ImageFileBytes, - request.ImageFileMime ?? "image/jpeg", - request.ImageFileName, - cancellationToken); + var result = await _fileManager.UploadImageAsync( + "Images/Products", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); - if (!string.IsNullOrWhiteSpace(mainPath)) - entity.ImagePath = mainPath; - - if (!string.IsNullOrWhiteSpace(thumbPath)) - entity.ThumbnailPath = thumbPath; - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to upload updated product image to FMS for product {ProductId}", request.Id); - } - } - else - { - // If no new file uploaded, keep existing paths or update from request - if (!string.IsNullOrWhiteSpace(request.ImagePath)) - entity.ImagePath = request.ImagePath; - if (!string.IsNullOrWhiteSpace(request.ThumbnailPath)) - entity.ThumbnailPath = request.ThumbnailPath; + entity.ImagePath = result.Main.Path; + entity.ThumbnailPath = result.Thumbnail.Path; } - // Handle separate thumbnail upload if provided + // آپلود بندانگشتی جداگانه (اختیاری — جایگزین بندانگشتی خودکار) if (request.ThumbnailFileBytes is { Length: > 0 }) { - try - { - var thumbPath = await _fileManagementService.UploadFileAsync( - "Images/Products/Thumbnails", - request.ThumbnailFileBytes, - request.ThumbnailFileMime ?? "image/jpeg", - request.ThumbnailFileName, - cancellationToken); + var thumbResult = await _fileManager.UploadAsync( + "Images/Products/Thumbnails", + request.ThumbnailFileBytes, + request.ThumbnailFileMime ?? "image/jpeg", + request.ThumbnailFileName, + cancellationToken); - if (!string.IsNullOrWhiteSpace(thumbPath)) - entity.ThumbnailPath = thumbPath; - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to upload updated product thumbnail to FMS for product {ProductId}", request.Id); - } + entity.ThumbnailPath = thumbResult.Path; } _context.Products.Update(entity); diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterQuery.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterQuery.cs index 802cfa0..ed3763f 100644 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterQuery.cs +++ b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterQuery.cs @@ -21,5 +21,6 @@ public class GetCustomerProductsByFilterQuery : IRequest? CategoryIds { get; set; } } diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterQueryHandler.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterQueryHandler.cs index 9253cda..9a1e371 100644 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterQueryHandler.cs +++ b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterQueryHandler.cs @@ -59,6 +59,9 @@ public class GetCustomerProductsByFilterQueryHandler : IRequestHandler x.ProductCategories.Any(pc => request.CategoryIds.Contains(pc.CategoryId))); + if (request.IsActive.HasValue) + query = query.Where(x => x.IsDeleted != request.IsActive.Value); + // Apply sorting if (!string.IsNullOrEmpty(request.SortBy)) query = query.ApplyOrder(request.SortBy); @@ -99,6 +102,7 @@ public class GetCustomerProductsByFilterQueryHandler : IRequestHandler new ProductCategoryPathModel { CategoryId = pc.CategoryId, diff --git a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterResponseDto.cs b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterResponseDto.cs index cdb672b..bccbcb8 100644 --- a/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterResponseDto.cs +++ b/src/CMSMicroservice.Application/ProductsCQ/Queries/GetCustomerProductsByFilter/GetCustomerProductsByFilterResponseDto.cs @@ -23,6 +23,7 @@ public class CustomerProductModel public int SaleCount { get; set; } public int ViewCount { get; set; } public int RemainingCount { get; set; } + public bool IsActive { get; set; } public List Categories { get; set; } } diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePage/CreateSitePageCommand.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePage/CreateSitePageCommand.cs new file mode 100644 index 0000000..1239233 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePage/CreateSitePageCommand.cs @@ -0,0 +1,18 @@ +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.CreateSitePage; + +public class CreateSitePageCommand : IRequest +{ + public string PageKey { get; set; } = default!; + public string Title { get; set; } = default!; + public string? MetaDescription { get; set; } + public string? HeroTitle { get; set; } + public string? HeroSubtitle { get; set; } + public bool IsActive { get; set; } = true; + + // Image upload properties + public byte[]? ImageFileBytes { get; set; } + public string? ImageFileMime { get; set; } + public string? ImageFileName { get; set; } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePage/CreateSitePageCommandHandler.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePage/CreateSitePageCommandHandler.cs new file mode 100644 index 0000000..ed50d10 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePage/CreateSitePageCommandHandler.cs @@ -0,0 +1,49 @@ +using CMSMicroservice.Application.Common.FileManager; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Content; +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.CreateSitePage; + +public class CreateSitePageCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IFileManager _fileManager; + + public CreateSitePageCommandHandler(IApplicationDbContext context, IFileManager fileManager) + { + _context = context; + _fileManager = fileManager; + } + + public async Task Handle(CreateSitePageCommand request, CancellationToken cancellationToken) + { + var entity = new SitePage + { + PageKey = request.PageKey, + Title = request.Title, + MetaDescription = request.MetaDescription, + HeroTitle = request.HeroTitle, + HeroSubtitle = request.HeroSubtitle, + IsActive = request.IsActive + }; + + // آپلود تصویر هیرو (اگر فایل ارسال شده باشد) + if (request.ImageFileBytes is { Length: > 0 }) + { + var result = await _fileManager.UploadImageAsync( + "Images/SitePages", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + entity.HeroImagePath = result.Main.Path; + } + + _context.SitePages.Add(entity); + await _context.SaveChangesAsync(cancellationToken); + + return entity.Id; + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommand.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommand.cs new file mode 100644 index 0000000..77f79dc --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommand.cs @@ -0,0 +1,21 @@ +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.CreateSitePageSection; + +public class CreateSitePageSectionCommand : IRequest +{ + public long SitePageId { get; set; } + public string SectionKey { get; set; } = default!; + public string? Title { get; set; } + public string? Subtitle { get; set; } + public string? HtmlContent { get; set; } + public string? IconName { get; set; } + public string? ImagePath { get; set; } + public bool IsActive { get; set; } = true; + public string? ExtraData { get; set; } + + // Image upload properties + public byte[]? ImageFileBytes { get; set; } + public string? ImageFileMime { get; set; } + public string? ImageFileName { get; set; } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommandHandler.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommandHandler.cs new file mode 100644 index 0000000..2f79325 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommandHandler.cs @@ -0,0 +1,68 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.FileManager; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Content; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.CreateSitePageSection; + +public class CreateSitePageSectionCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IFileManager _fileManager; + + public CreateSitePageSectionCommandHandler(IApplicationDbContext context, IFileManager fileManager) + { + _context = context; + _fileManager = fileManager; + } + + public async Task Handle(CreateSitePageSectionCommand request, CancellationToken cancellationToken) + { + var page = await _context.SitePages.FirstOrDefaultAsync(x => x.Id == request.SitePageId && !x.IsDeleted, cancellationToken); + if (page == null) + throw new NotFoundException(nameof(SitePage), request.SitePageId); + + var maxSortOrder = await _context.SitePageSections + .Where(x => x.SitePageId == request.SitePageId && !x.IsDeleted) + .MaxAsync(x => (int?)x.SortOrder, cancellationToken) ?? 0; + + var sectionKey = string.IsNullOrWhiteSpace(request.SectionKey) + ? $"section-{Guid.NewGuid():N}"[..20] + : request.SectionKey.Trim().ToLower(); + + var entity = new SitePageSection + { + SitePageId = request.SitePageId, + SectionKey = sectionKey, + Title = request.Title, + Subtitle = request.Subtitle, + HtmlContent = request.HtmlContent, + IconName = request.IconName, + ImagePath = request.ImagePath, + SortOrder = maxSortOrder + 1, + IsActive = request.IsActive, + ExtraData = request.ExtraData + }; + + // آپلود تصویر بخش (اگر فایل ارسال شده باشد) + if (request.ImageFileBytes is { Length: > 0 }) + { + var result = await _fileManager.UploadImageAsync( + "Images/SitePageSections", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + entity.ImagePath = result.Main.Path; + entity.ImageThumbnailPath = result.Thumbnail.Path; + } + + _context.SitePageSections.Add(entity); + await _context.SaveChangesAsync(cancellationToken); + + return entity.Id; + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommandValidator.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommandValidator.cs new file mode 100644 index 0000000..e8fe36c --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/CreateSitePageSection/CreateSitePageSectionCommandValidator.cs @@ -0,0 +1,26 @@ +using FluentValidation; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.CreateSitePageSection; + +public class CreateSitePageSectionCommandValidator : AbstractValidator +{ + public CreateSitePageSectionCommandValidator() + { + RuleFor(x => x.SitePageId) + .GreaterThan(0).WithMessage("شناسه صفحه نامعتبر است"); + + RuleFor(x => x.SectionKey) + .MaximumLength(100).WithMessage("کلید بخش حداکثر ۱۰۰ کاراکتر") + .Matches(@"^[a-z0-9\-_]*$").WithMessage("کلید بخش فقط شامل حروف کوچک، اعداد، خط تیره و زیرخط") + .When(x => !string.IsNullOrWhiteSpace(x.SectionKey)); + + RuleFor(x => x.Title) + .MaximumLength(300).WithMessage("عنوان بخش حداکثر ۳۰۰ کاراکتر"); + + RuleFor(x => x.Subtitle) + .MaximumLength(500).WithMessage("زیرعنوان بخش حداکثر ۵۰۰ کاراکتر"); + + RuleFor(x => x.IconName) + .MaximumLength(100).WithMessage("نام آیکون حداکثر ۱۰۰ کاراکتر"); + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePage/DeleteSitePageCommand.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePage/DeleteSitePageCommand.cs new file mode 100644 index 0000000..1b447da --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePage/DeleteSitePageCommand.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.DeleteSitePage; + +public class DeleteSitePageCommand : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePage/DeleteSitePageCommandHandler.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePage/DeleteSitePageCommandHandler.cs new file mode 100644 index 0000000..75cdc0e --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePage/DeleteSitePageCommandHandler.cs @@ -0,0 +1,35 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Content; +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.DeleteSitePage; + +public class DeleteSitePageCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public DeleteSitePageCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(DeleteSitePageCommand request, CancellationToken cancellationToken) + { + var entity = await _context.SitePages.FindAsync(new object[] { request.Id }, cancellationToken); + if (entity == null || entity.IsDeleted) + throw new NotFoundException(nameof(SitePage), request.Id); + + entity.IsDeleted = true; + + // حذف نرم بخش‌های وابسته + foreach (var section in entity.Sections.Where(s => !s.IsDeleted)) + { + section.IsDeleted = true; + } + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePageSection/DeleteSitePageSectionCommand.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePageSection/DeleteSitePageSectionCommand.cs new file mode 100644 index 0000000..9b16a0d --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePageSection/DeleteSitePageSectionCommand.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.DeleteSitePageSection; + +public class DeleteSitePageSectionCommand : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePageSection/DeleteSitePageSectionCommandHandler.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePageSection/DeleteSitePageSectionCommandHandler.cs new file mode 100644 index 0000000..aa5f08f --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/DeleteSitePageSection/DeleteSitePageSectionCommandHandler.cs @@ -0,0 +1,29 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Content; +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.DeleteSitePageSection; + +public class DeleteSitePageSectionCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public DeleteSitePageSectionCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(DeleteSitePageSectionCommand request, CancellationToken cancellationToken) + { + var entity = await _context.SitePageSections.FindAsync(new object[] { request.Id }, cancellationToken); + if (entity == null || entity.IsDeleted) + throw new NotFoundException(nameof(SitePageSection), request.Id); + + entity.IsDeleted = true; + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/ReorderSitePageSections/ReorderSitePageSectionsCommand.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/ReorderSitePageSections/ReorderSitePageSectionsCommand.cs new file mode 100644 index 0000000..3231038 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/ReorderSitePageSections/ReorderSitePageSectionsCommand.cs @@ -0,0 +1,14 @@ +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.ReorderSitePageSections; + +public class ReorderSitePageSectionsCommand : IRequest +{ + public List Items { get; set; } = new(); +} + +public class SectionSortItem +{ + public long Id { get; set; } + public int SortOrder { get; set; } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/ReorderSitePageSections/ReorderSitePageSectionsCommandHandler.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/ReorderSitePageSections/ReorderSitePageSectionsCommandHandler.cs new file mode 100644 index 0000000..7d7844a --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/ReorderSitePageSections/ReorderSitePageSectionsCommandHandler.cs @@ -0,0 +1,34 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.ReorderSitePageSections; + +public class ReorderSitePageSectionsCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public ReorderSitePageSectionsCommandHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(ReorderSitePageSectionsCommand request, CancellationToken cancellationToken) + { + var ids = request.Items.Select(x => x.Id).ToList(); + var sections = await _context.SitePageSections + .Where(x => ids.Contains(x.Id) && !x.IsDeleted) + .ToListAsync(cancellationToken); + + foreach (var item in request.Items) + { + var section = sections.FirstOrDefault(x => x.Id == item.Id); + if (section != null) + section.SortOrder = item.SortOrder; + } + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePage/UpdateSitePageCommand.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePage/UpdateSitePageCommand.cs new file mode 100644 index 0000000..b9dc42e --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePage/UpdateSitePageCommand.cs @@ -0,0 +1,19 @@ +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.UpdateSitePage; + +public class UpdateSitePageCommand : IRequest +{ + public long Id { get; set; } + public string Title { get; set; } = default!; + public string? MetaDescription { get; set; } + public string? HeroTitle { get; set; } + public string? HeroSubtitle { get; set; } + public string? HeroImagePath { get; set; } + public bool IsActive { get; set; } + + // Image upload properties + public byte[]? ImageFileBytes { get; set; } + public string? ImageFileMime { get; set; } + public string? ImageFileName { get; set; } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePage/UpdateSitePageCommandHandler.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePage/UpdateSitePageCommandHandler.cs new file mode 100644 index 0000000..289b600 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePage/UpdateSitePageCommandHandler.cs @@ -0,0 +1,50 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.FileManager; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Content; +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.UpdateSitePage; + +public class UpdateSitePageCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IFileManager _fileManager; + + public UpdateSitePageCommandHandler(IApplicationDbContext context, IFileManager fileManager) + { + _context = context; + _fileManager = fileManager; + } + + public async Task Handle(UpdateSitePageCommand request, CancellationToken cancellationToken) + { + var entity = await _context.SitePages.FindAsync(new object[] { request.Id }, cancellationToken); + if (entity == null || entity.IsDeleted) + throw new NotFoundException(nameof(SitePage), request.Id); + + entity.Title = request.Title; + entity.MetaDescription = request.MetaDescription; + entity.HeroTitle = request.HeroTitle; + entity.HeroSubtitle = request.HeroSubtitle; + entity.HeroImagePath = request.HeroImagePath; + entity.IsActive = request.IsActive; + + // آپلود تصویر هیرو (اگر فایل ارسال شده باشد) + if (request.ImageFileBytes is { Length: > 0 }) + { + var result = await _fileManager.UploadImageAsync( + "Images/SitePages", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + entity.HeroImagePath = result.Main.Path; + } + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePage/UpdateSitePageCommandValidator.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePage/UpdateSitePageCommandValidator.cs new file mode 100644 index 0000000..32cc616 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePage/UpdateSitePageCommandValidator.cs @@ -0,0 +1,25 @@ +using FluentValidation; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.UpdateSitePage; + +public class UpdateSitePageCommandValidator : AbstractValidator +{ + public UpdateSitePageCommandValidator() + { + RuleFor(x => x.Id) + .GreaterThan(0).WithMessage("شناسه صفحه نامعتبر است"); + + RuleFor(x => x.Title) + .NotEmpty().WithMessage("عنوان صفحه الزامی است") + .MaximumLength(200).WithMessage("عنوان صفحه حداکثر ۲۰۰ کاراکتر"); + + RuleFor(x => x.MetaDescription) + .MaximumLength(500).WithMessage("توضیحات متا حداکثر ۵۰۰ کاراکتر"); + + RuleFor(x => x.HeroTitle) + .MaximumLength(300).WithMessage("عنوان هیرو حداکثر ۳۰۰ کاراکتر"); + + RuleFor(x => x.HeroSubtitle) + .MaximumLength(500).WithMessage("زیرعنوان هیرو حداکثر ۵۰۰ کاراکتر"); + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePageSection/UpdateSitePageSectionCommand.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePageSection/UpdateSitePageSectionCommand.cs new file mode 100644 index 0000000..be6f6c7 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePageSection/UpdateSitePageSectionCommand.cs @@ -0,0 +1,22 @@ +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.UpdateSitePageSection; + +public class UpdateSitePageSectionCommand : IRequest +{ + public long Id { get; set; } + public string SectionKey { get; set; } = default!; + public string? Title { get; set; } + public string? Subtitle { get; set; } + public string? HtmlContent { get; set; } + public string? IconName { get; set; } + public string? ImagePath { get; set; } + public int SortOrder { get; set; } + public bool IsActive { get; set; } + public string? ExtraData { get; set; } + + // Image upload properties + public byte[]? ImageFileBytes { get; set; } + public string? ImageFileMime { get; set; } + public string? ImageFileName { get; set; } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePageSection/UpdateSitePageSectionCommandHandler.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePageSection/UpdateSitePageSectionCommandHandler.cs new file mode 100644 index 0000000..ad58697 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePageSection/UpdateSitePageSectionCommandHandler.cs @@ -0,0 +1,54 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.FileManager; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Content; +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.UpdateSitePageSection; + +public class UpdateSitePageSectionCommandHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + private readonly IFileManager _fileManager; + + public UpdateSitePageSectionCommandHandler(IApplicationDbContext context, IFileManager fileManager) + { + _context = context; + _fileManager = fileManager; + } + + public async Task Handle(UpdateSitePageSectionCommand request, CancellationToken cancellationToken) + { + var entity = await _context.SitePageSections.FindAsync(new object[] { request.Id }, cancellationToken); + if (entity == null || entity.IsDeleted) + throw new NotFoundException(nameof(SitePageSection), request.Id); + + entity.SectionKey = request.SectionKey; + entity.Title = request.Title; + entity.Subtitle = request.Subtitle; + entity.HtmlContent = request.HtmlContent; + entity.IconName = request.IconName; + entity.ImagePath = request.ImagePath; + entity.SortOrder = request.SortOrder; + entity.IsActive = request.IsActive; + entity.ExtraData = request.ExtraData; + + // آپلود تصویر بخش (اگر فایل جدید ارسال شده باشد) + if (request.ImageFileBytes is { Length: > 0 }) + { + var result = await _fileManager.UploadImageAsync( + "Images/SitePageSections", + request.ImageFileBytes, + request.ImageFileMime ?? "image/jpeg", + request.ImageFileName, + cancellationToken); + + entity.ImagePath = result.Main.Path; + entity.ImageThumbnailPath = result.Thumbnail.Path; + } + + await _context.SaveChangesAsync(cancellationToken); + + return Unit.Value; + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePageSection/UpdateSitePageSectionCommandValidator.cs b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePageSection/UpdateSitePageSectionCommandValidator.cs new file mode 100644 index 0000000..2a5b665 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Commands/UpdateSitePageSection/UpdateSitePageSectionCommandValidator.cs @@ -0,0 +1,26 @@ +using FluentValidation; + +namespace CMSMicroservice.Application.SitePageCQ.Commands.UpdateSitePageSection; + +public class UpdateSitePageSectionCommandValidator : AbstractValidator +{ + public UpdateSitePageSectionCommandValidator() + { + RuleFor(x => x.Id) + .GreaterThan(0).WithMessage("شناسه بخش نامعتبر است"); + + RuleFor(x => x.SectionKey) + .NotEmpty().WithMessage("کلید بخش الزامی است") + .MaximumLength(100).WithMessage("کلید بخش حداکثر ۱۰۰ کاراکتر") + .Matches(@"^[a-z0-9\-_]+$").WithMessage("کلید بخش فقط شامل حروف کوچک، اعداد، خط تیره و زیرخط"); + + RuleFor(x => x.Title) + .MaximumLength(300).WithMessage("عنوان بخش حداکثر ۳۰۰ کاراکتر"); + + RuleFor(x => x.Subtitle) + .MaximumLength(500).WithMessage("زیرعنوان بخش حداکثر ۵۰۰ کاراکتر"); + + RuleFor(x => x.IconName) + .MaximumLength(100).WithMessage("نام آیکون حداکثر ۱۰۰ کاراکتر"); + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Queries/GetAllSitePages/GetAllSitePagesQuery.cs b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetAllSitePages/GetAllSitePagesQuery.cs new file mode 100644 index 0000000..7f4d4ba --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetAllSitePages/GetAllSitePagesQuery.cs @@ -0,0 +1,51 @@ +using CMSMicroservice.Application.Common.Interfaces; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.SitePageCQ.Queries.GetAllSitePages; + +public class GetAllSitePagesQuery : IRequest> +{ +} + +public class SitePageListItemDto +{ + public long Id { get; set; } + public string PageKey { get; set; } = default!; + public string Title { get; set; } = default!; + public bool IsActive { get; set; } + public int SectionCount { get; set; } + public DateTime Created { get; set; } + public DateTime? LastModified { get; set; } +} + +public class GetAllSitePagesQueryHandler : IRequestHandler> +{ + private readonly IApplicationDbContext _context; + + public GetAllSitePagesQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task> Handle(GetAllSitePagesQuery request, CancellationToken cancellationToken) + { + var pages = await _context.SitePages + .Include(x => x.Sections) + .Where(x => !x.IsDeleted) + .OrderBy(x => x.PageKey) + .Select(x => new SitePageListItemDto + { + Id = x.Id, + PageKey = x.PageKey, + Title = x.Title, + IsActive = x.IsActive, + SectionCount = x.Sections.Count(s => !s.IsDeleted), + Created = x.Created, + LastModified = x.LastModified + }) + .ToListAsync(cancellationToken); + + return pages; + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePage/GetSitePageQuery.cs b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePage/GetSitePageQuery.cs new file mode 100644 index 0000000..a83dc25 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePage/GetSitePageQuery.cs @@ -0,0 +1,8 @@ +using MediatR; + +namespace CMSMicroservice.Application.SitePageCQ.Queries.GetSitePage; + +public class GetSitePageQuery : IRequest +{ + public long Id { get; set; } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePage/GetSitePageQueryHandler.cs b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePage/GetSitePageQueryHandler.cs new file mode 100644 index 0000000..f01ca01 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePage/GetSitePageQueryHandler.cs @@ -0,0 +1,59 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Content; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.SitePageCQ.Queries.GetSitePage; + +public class GetSitePageQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetSitePageQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetSitePageQuery request, CancellationToken cancellationToken) + { + var entity = await _context.SitePages + .Include(x => x.Sections.Where(s => !s.IsDeleted).OrderBy(s => s.SortOrder)) + .FirstOrDefaultAsync(x => x.Id == request.Id && !x.IsDeleted, cancellationToken); + + if (entity == null) + throw new NotFoundException(nameof(SitePage), request.Id); + + return MapToDto(entity); + } + + internal static SitePageDto MapToDto(SitePage entity) + { + return new SitePageDto + { + Id = entity.Id, + PageKey = entity.PageKey, + Title = entity.Title, + MetaDescription = entity.MetaDescription, + HeroTitle = entity.HeroTitle, + HeroSubtitle = entity.HeroSubtitle, + HeroImagePath = entity.HeroImagePath, + IsActive = entity.IsActive, + Created = entity.Created, + LastModified = entity.LastModified, + Sections = entity.Sections.Select(s => new SitePageSectionDto + { + Id = s.Id, + SectionKey = s.SectionKey, + Title = s.Title, + Subtitle = s.Subtitle, + HtmlContent = s.HtmlContent, + IconName = s.IconName, + ImagePath = s.ImagePath, + SortOrder = s.SortOrder, + IsActive = s.IsActive, + ExtraData = s.ExtraData + }).ToList() + }; + } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePage/SitePageDto.cs b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePage/SitePageDto.cs new file mode 100644 index 0000000..2aa46c7 --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePage/SitePageDto.cs @@ -0,0 +1,30 @@ +namespace CMSMicroservice.Application.SitePageCQ.Queries.GetSitePage; + +public class SitePageDto +{ + public long Id { get; set; } + public string PageKey { get; set; } = default!; + public string Title { get; set; } = default!; + public string? MetaDescription { get; set; } + public string? HeroTitle { get; set; } + public string? HeroSubtitle { get; set; } + public string? HeroImagePath { get; set; } + public bool IsActive { get; set; } + public DateTime Created { get; set; } + public DateTime? LastModified { get; set; } + public List Sections { get; set; } = new(); +} + +public class SitePageSectionDto +{ + public long Id { get; set; } + public string SectionKey { get; set; } = default!; + public string? Title { get; set; } + public string? Subtitle { get; set; } + public string? HtmlContent { get; set; } + public string? IconName { get; set; } + public string? ImagePath { get; set; } + public int SortOrder { get; set; } + public bool IsActive { get; set; } + public string? ExtraData { get; set; } +} diff --git a/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePageByKey/GetSitePageByKeyQuery.cs b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePageByKey/GetSitePageByKeyQuery.cs new file mode 100644 index 0000000..ffd890b --- /dev/null +++ b/src/CMSMicroservice.Application/SitePageCQ/Queries/GetSitePageByKey/GetSitePageByKeyQuery.cs @@ -0,0 +1,34 @@ +using CMSMicroservice.Application.Common.Exceptions; +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Domain.Entities.Content; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace CMSMicroservice.Application.SitePageCQ.Queries.GetSitePageByKey; + +public class GetSitePageByKeyQuery : IRequest +{ + public string PageKey { get; set; } = default!; +} + +public class GetSitePageByKeyQueryHandler : IRequestHandler +{ + private readonly IApplicationDbContext _context; + + public GetSitePageByKeyQueryHandler(IApplicationDbContext context) + { + _context = context; + } + + public async Task Handle(GetSitePageByKeyQuery request, CancellationToken cancellationToken) + { + var entity = await _context.SitePages + .Include(x => x.Sections.Where(s => !s.IsDeleted).OrderBy(s => s.SortOrder)) + .FirstOrDefaultAsync(x => x.PageKey == request.PageKey && !x.IsDeleted, cancellationToken); + + if (entity == null) + throw new NotFoundException(nameof(SitePage), request.PageKey); + + return GetSitePage.GetSitePageQueryHandler.MapToDto(entity); + } +} diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractCommandHandler.cs b/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractCommandHandler.cs index c352c34..ccd1322 100644 --- a/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractCommandHandler.cs +++ b/src/CMSMicroservice.Application/UserCQ/Commands/AcceptContract/AcceptContractCommandHandler.cs @@ -24,15 +24,9 @@ public class AcceptContractCommandHandler : IRequestHandler Handle(AcceptContractCommand request, CancellationToken cancellationToken) { - // Verify OTP first - var otpToken = await _context.OtpTokens - .Where(x => x.Mobile == _currentUserService.Username && x.Purpose == "signContract" && !x.IsUsed) - .OrderByDescending(x => x.Id) - .FirstOrDefaultAsync(cancellationToken); - - var secret = _cfg["Otp:Secret"] ?? throw new InvalidOperationException("Otp:Secret not set"); - if (otpToken == null || !otpToken.IsValid() || !_hashService.VerifyHmacSha256Hex(request.Code, otpToken.CodeHash, secret)) - return new AcceptContractResponseDto { IsSuccess = false, Message = "کد تایید نامعتبر است" }; + // پیدا کردن کاربر بر اساس UserId از توکن JWT + if (!long.TryParse(_currentUserService.UserId, out var userId)) + return new AcceptContractResponseDto { IsSuccess = false, Message = "کاربر احراز هویت نشده است" }; var user = await _context.Users .Include(u => u.UserContracts) @@ -40,12 +34,22 @@ public class AcceptContractCommandHandler : IRequestHandler u.UserRoles) .ThenInclude(ur => ur.Role) .Include(u => u.ClubMembership) - .Where(x => x.Mobile == _currentUserService.Username) + .Where(x => x.Id == userId) .FirstOrDefaultAsync(cancellationToken); if (user == null) return new AcceptContractResponseDto { IsSuccess = false, Message = "کاربر یافت نشد" }; + // جستجوی OTP بر اساس شماره موبایل کاربر (نه Username) + var otpToken = await _context.OtpTokens + .Where(x => x.Mobile == user.Mobile && x.Purpose == "signContract" && !x.IsUsed) + .OrderByDescending(x => x.Id) + .FirstOrDefaultAsync(cancellationToken); + + var secret = _cfg["Otp:Secret"] ?? throw new InvalidOperationException("Otp:Secret not set"); + if (otpToken == null || !otpToken.IsValid() || !_hashService.VerifyHmacSha256Hex(request.Code, otpToken.CodeHash, secret)) + return new AcceptContractResponseDto { IsSuccess = false, Message = "کد تایید نامعتبر است" }; + // Create user contract var userContract = new UserContract { @@ -62,6 +66,16 @@ public class AcceptContractCommandHandler : IRequestHandler u.UserContracts) + .ThenInclude(uc => uc.Contract) + .Include(u => u.UserRoles) + .ThenInclude(ur => ur.Role) + .Include(u => u.ClubMembership) + .Where(x => x.Id == userId) + .FirstAsync(cancellationToken); + // Generate JWT token with updated contract status var token = await _generateJwt.GenerateJwtToken(user); diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommandHandler.cs b/src/CMSMicroservice.Application/UserCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommandHandler.cs index a6e0248..c9d9603 100644 --- a/src/CMSMicroservice.Application/UserCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommandHandler.cs +++ b/src/CMSMicroservice.Application/UserCQ/Commands/CreateNewOtpToken/CreateNewOtpTokenCommandHandler.cs @@ -1,6 +1,8 @@ +using System.Security.Cryptography; using System.Text; using CMSMicroservice.Application.Common.Interfaces; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using CMSMicroservice.Domain.Entities; namespace CMSMicroservice.Application.UserCQ.Commands.CreateNewOtpToken; @@ -9,41 +11,60 @@ public class CreateNewOtpTokenCommandHandler : IRequestHandler Handle(CreateNewOtpTokenCommand request, CancellationToken cancellationToken) { - // Generate random 4-digit code - var random = new Random(); - var code = random.Next(1000, 9999).ToString(); + var mobile = request.Mobile.NormalizeIranMobile(); + var purpose = request.Purpose?.ToLowerInvariant() ?? "login"; + var now = DateTime.Now; - // Invalidate previous unused tokens for this mobile and purpose - var existingTokens = await _context.OtpTokens - .Where(x => x.Mobile == request.Mobile && x.Purpose == request.Purpose && !x.IsUsed) - .ToListAsync(cancellationToken); + // ریت‌لیمیت: اگر هنوز کدی فعال و تازه داریم، اجازه نده + var lastActive = await _context.OtpTokens + .Where(o => o.Mobile == mobile && o.Purpose == purpose && !o.IsUsed && o.ExpiresAt > now) + .OrderByDescending(o => o.Created) + .FirstOrDefaultAsync(cancellationToken); - foreach (var token in existingTokens) - { - token.IsUsed = true; - } + if (lastActive is not null && (now - lastActive.Created) < Cooldown) + return new CreateNewOtpTokenResponseDto + { + Success = false, + Message = "لطفاً کمی بعد دوباره تلاش کنید." + }; + + // تولید کد ۶ رقمی امن + var code = GenerateNumericCode(CodeLength); + var secret = _cfg["Otp:Secret"] ?? throw new InvalidOperationException("Otp:Secret not set"); + var codeHash = _hashService.ComputeHmacSha256Hex(code, secret); // Create new OTP token var otpToken = new OtpToken { - Mobile = request.Mobile, - Purpose = request.Purpose, - Code = code, - CodeHash = BCrypt.Net.BCrypt.HashPassword(code), // Hash the code for security + Mobile = mobile, + Purpose = purpose, + CodeHash = codeHash, IsUsed = false, - ExpiresAt = DateTime.UtcNow.AddMinutes(5) // 5 minutes expiry + Attempts = 0, + ExpiresAt = now.Add(Ttl) }; _context.OtpTokens.Add(otpToken); @@ -51,24 +72,38 @@ public class CreateNewOtpTokenCommandHandler : IRequestHandler x.Mobile == request.Mobile) - .FirstOrDefaultAsync(cancellationToken); - - await _kavenegarService.VerifyLookupAsync(request.Mobile, code); + // برای امضای قرارداد، شناسه GUID هم در پیامک ارسال شود + if ((purpose == "signcontract" || purpose == "signclubcontract") && !string.IsNullOrEmpty(request.SignGuid)) + { + var message = $"کد تایید امضای قرارداد: {code}\nشناسه قرارداد: {request.SignGuid}"; + await _kavenegarService.SendAsync(mobile, message); + } + else + { + await _kavenegarService.VerifyLookupAsync(mobile, code); + } } catch (Exception) { // Log error but don't fail the request - // TODO: Add proper logging } return new CreateNewOtpTokenResponseDto { Success = true, Message = "کد تایید با موفقیت ارسال شد", + RemainingAttempts = MaxAttempts, + RemainingSeconds = (int)Ttl.TotalSeconds, ExpiresAt = otpToken.ExpiresAt }; } + + private static string GenerateNumericCode(int len) + { + var bytes = new byte[len]; + RandomNumberGenerator.Fill(bytes); + var sb = new StringBuilder(len); + foreach (var b in bytes) sb.Append((b % 10).ToString()); + return sb.ToString(); + } } \ No newline at end of file diff --git a/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs b/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs index 9ee9bf4..01cc350 100644 --- a/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs +++ b/src/CMSMicroservice.Application/UserCQ/Commands/VerifyOtpToken/VerifyOtpTokenCommandHandler.cs @@ -1,5 +1,4 @@ -using CMSMicroservice.Application.Common.Interfaces; -using Microsoft.EntityFrameworkCore; +using CMSMicroservice.Domain.Events; using Microsoft.Extensions.Configuration; namespace CMSMicroservice.Application.UserCQ.Commands.VerifyOtpToken; @@ -19,36 +18,134 @@ public class VerifyOtpTokenCommandHandler : IRequestHandler Handle(VerifyOtpTokenCommand request, CancellationToken cancellationToken) { + var mobile = request.Mobile.NormalizeIranMobile(); + var purpose = request.Purpose?.ToLowerInvariant() ?? "login"; + var now = DateTime.Now; + var otpToken = await _context.OtpTokens - .Where(x => x.Mobile == request.Mobile && x.Purpose == request.Purpose && !x.IsUsed) + .Where(x => x.Mobile == mobile && x.Purpose == purpose && !x.IsUsed && x.ExpiresAt > now) .OrderByDescending(x => x.Id) .FirstOrDefaultAsync(cancellationToken); if (otpToken == null) - return new VerifyOtpTokenResponseDto { Success = false, Message = "کد تایید نامعتبر است" }; + return new VerifyOtpTokenResponseDto { Success = false, Message = "کد تایید منقضی شده یا وجود ندارد. لطفاً کد جدید دریافت کنید." }; - // Check expiry and usage - if (otpToken.IsUsed || DateTime.Now > otpToken.ExpiresAt) - return new VerifyOtpTokenResponseDto { Success = false, Message = "کد تایید منقضی شده است" }; + // بررسی تعداد تلاش + if (otpToken.Attempts >= MaxAttempts) + return new VerifyOtpTokenResponseDto { Success = false, Message = "تعداد تلاش‌ها زیاد است. لطفاً کد جدید دریافت کنید." }; - // Verify using the same HMAC-SHA256 method used during creation + otpToken.Attempts++; + + // Verify using HMAC-SHA256 var secret = _cfg["Otp:Secret"] ?? throw new InvalidOperationException("Otp:Secret not set"); if (!_hashService.VerifyHmacSha256Hex(request.Code, otpToken.CodeHash, secret)) - return new VerifyOtpTokenResponseDto { Success = false, Message = "کد تایید نامعتبر است" }; + { + await _context.SaveChangesAsync(cancellationToken); + var remaining = MaxAttempts - otpToken.Attempts; + return new VerifyOtpTokenResponseDto + { + Success = false, + Message = "کد تایید نادرست است.", + RemainingAttempts = remaining + }; + } + // ── جستجوی کاربر ── var user = await _context.Users .Include(u => u.UserContracts) .ThenInclude(uc => uc.Contract) .Include(u => u.UserRoles) .ThenInclude(ur => ur.Role) .Include(u => u.ClubMembership) - .Where(x => x.Mobile == request.Mobile) + .Where(x => x.Mobile == mobile) .FirstOrDefaultAsync(cancellationToken); + // ── کاربر وجود ندارد → ثبت‌نام جدید ── if (user == null) - return new VerifyOtpTokenResponseDto { Success = false, Message = "کاربر یافت نشد" }; + { + // کد معرف الزامی است + if (string.IsNullOrWhiteSpace(request.ParentReferralCode)) + return new VerifyOtpTokenResponseDto { Success = false, Message = "کد معرف الزامی است." }; + + // بررسی وجود معرف و فعال بودن عضویت باشگاه + var parent = await _context.Users + .Include(u => u.ClubMembership) + .FirstOrDefaultAsync(u => u.ReferralCode == request.ParentReferralCode, cancellationToken); + + if (parent == null) + return new VerifyOtpTokenResponseDto { Success = false, Message = "معرف وجود ندارد." }; + + if (parent.ClubMembership == null || !parent.ClubMembership.IsActive) + return new VerifyOtpTokenResponseDto + { + Success = false, + Message = "لینک دعوت معرف فعال نیست. لطفاً از کد دعوت معتبر دیگری استفاده کنید." + }; + + // بررسی ظرفیت معرف (حداکثر ۲ زیرمجموعه مستقیم) + var existingChildren = await _context.Users + .Where(x => x.NetworkParentId == parent.Id && !x.IsDeleted) + .Select(x => x.LegPosition) + .ToListAsync(cancellationToken); + + if (existingChildren.Count > 1) + return new VerifyOtpTokenResponseDto { Success = false, Message = "ظرفیت معرف تکمیل است!!" }; + + // تعیین موقعیت شاخه (چپ اول، بعد راست) + NetworkLeg newUserLegPosition; + if (!existingChildren.Any(x => x == NetworkLeg.Left)) + newUserLegPosition = NetworkLeg.Left; + else if (!existingChildren.Any(x => x == NetworkLeg.Right)) + newUserLegPosition = NetworkLeg.Right; + else + return new VerifyOtpTokenResponseDto { Success = false, Message = "ظرفیت معرف تکمیل است!!" }; + + // ایجاد کاربر جدید + user = new User + { + Mobile = mobile, + ReferralCode = UtilExtensions.Generate(digits: 10, firstDigitNonZero: true), + IsMobileVerified = true, + MobileVerifiedAt = now, + IsRulesAccepted = true, + RulesAcceptedAt = now, + NetworkParentId = parent.Id, + LegPosition = newUserLegPosition + }; + await _context.Users.AddAsync(user, cancellationToken); + user.AddDomainEvent(new CreateNewUserEvent(user)); + await _context.SaveChangesAsync(cancellationToken); + + // ایجاد نقش کاربری + var userRole = new UserRole { UserId = user.Id, RoleId = 1 }; + await _context.UserRoles.AddAsync(userRole, cancellationToken); + user.AddDomainEvent(new CreateNewUserRoleEvent(userRole)); + + // ایجاد کیف پول + var userWallet = new UserWallet { UserId = user.Id, Balance = 0, NetworkBalance = 0 }; + await _context.UserWallets.AddAsync(userWallet, cancellationToken); + user.AddDomainEvent(new CreateNewUserWalletEvent(userWallet)); + await _context.SaveChangesAsync(cancellationToken); + + // بارگذاری مجدد کاربر با روابط کامل (برای تولید توکن) + user = await _context.Users + .Include(u => u.UserContracts) + .ThenInclude(uc => uc.Contract) + .Include(u => u.UserRoles) + .ThenInclude(ur => ur.Role) + .Include(u => u.ClubMembership) + .FirstAsync(x => x.Id == user.Id, cancellationToken); + } + else + { + // کاربر موجود — به‌روزرسانی وضعیت تایید موبایل + user.IsMobileVerified = true; + user.MobileVerifiedAt ??= now; + } // Mark OTP as used otpToken.IsUsed = true; @@ -61,7 +158,8 @@ public class VerifyOtpTokenCommandHandler : IRequestHandler +/// دسته‌بندی مقالات بلاگ +/// +public class BlogCategory : BaseAuditableEntity +{ + //عنوان دسته‌بندی + public string Title { get; set; } = string.Empty; + //نشانی یکتا + public string Slug { get; set; } = string.Empty; + //توضیحات + public string? Description { get; set; } + //نام آیکون Material + public string? IconName { get; set; } + //ترتیب نمایش + public int SortOrder { get; set; } + //فعال؟ + public bool IsActive { get; set; } = true; + + //مقالات + public virtual ICollection BlogPostCategories { get; set; } = new List(); +} diff --git a/src/CMSMicroservice.Domain/Entities/Blog/BlogPost.cs b/src/CMSMicroservice.Domain/Entities/Blog/BlogPost.cs new file mode 100644 index 0000000..2a4ddf4 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/Blog/BlogPost.cs @@ -0,0 +1,45 @@ +using CMSMicroservice.Domain.Common; +using CMSMicroservice.Domain.Enums; + +namespace CMSMicroservice.Domain.Entities.Blog; + +/// +/// مقاله بلاگ +/// شامل عنوان، خلاصه، محتوای HTML، تصویر شاخص، وضعیت انتشار و آمار بازدید +/// +public class BlogPost : BaseAuditableEntity +{ + //عنوان مقاله + public string Title { get; set; } = string.Empty; + //نشانی یکتا (slug) + public string Slug { get; set; } = string.Empty; + //خلاصه مقاله برای نمایش در کارت‌ها + public string? Summary { get; set; } + //محتوای HTML مقاله + public string HtmlContent { get; set; } = string.Empty; + //مسیر تصویر شاخص + public string? FeaturedImagePath { get; set; } + //مسیر تامبنیل تصویر شاخص + public string? FeaturedImageThumbnailPath { get; set; } + //وضعیت مقاله + public BlogPostStatus Status { get; set; } = BlogPostStatus.Draft; + //زمان انتشار + public DateTime? PublishedAt { get; set; } + //زمانبندی انتشار خودکار + public DateTime? ScheduledPublishAt { get; set; } + //تعداد بازدید + public int ViewCount { get; set; } = 0; + //شناسه نویسنده + public long AuthorUserId { get; set; } + //نمایش در صفحه اول + public bool IsFeatured { get; set; } = false; + //ترتیب نمایش + public int SortOrder { get; set; } + + //دسته‌بندی‌ها + public virtual ICollection BlogPostCategories { get; set; } = new List(); + //تگ‌ها + public virtual ICollection BlogPostTags { get; set; } = new List(); + //تصاویر گالری + public virtual ICollection BlogPostImages { get; set; } = new List(); +} diff --git a/src/CMSMicroservice.Domain/Entities/Blog/BlogPostCategory.cs b/src/CMSMicroservice.Domain/Entities/Blog/BlogPostCategory.cs new file mode 100644 index 0000000..c237e72 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/Blog/BlogPostCategory.cs @@ -0,0 +1,15 @@ +using CMSMicroservice.Domain.Common; + +namespace CMSMicroservice.Domain.Entities.Blog; + +/// +/// جدول واسط بین مقاله و دسته‌بندی (چند به چند) +/// +public class BlogPostCategory : BaseEntity +{ + public long BlogPostId { get; set; } + public long BlogCategoryId { get; set; } + + public virtual BlogPost BlogPost { get; set; } = null!; + public virtual BlogCategory BlogCategory { get; set; } = null!; +} diff --git a/src/CMSMicroservice.Domain/Entities/Blog/BlogPostImage.cs b/src/CMSMicroservice.Domain/Entities/Blog/BlogPostImage.cs new file mode 100644 index 0000000..8215454 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/Blog/BlogPostImage.cs @@ -0,0 +1,23 @@ +using CMSMicroservice.Domain.Common; + +namespace CMSMicroservice.Domain.Entities.Blog; + +/// +/// تصاویر گالری مقاله +/// +public class BlogPostImage : BaseAuditableEntity +{ + public long BlogPostId { get; set; } + //مسیر تصویر + public string ImagePath { get; set; } = string.Empty; + //مسیر تامبنیل + public string ThumbnailPath { get; set; } = string.Empty; + //متن جایگزین + public string? AltText { get; set; } + //عنوان تصویر + public string? Caption { get; set; } + //ترتیب نمایش + public int SortOrder { get; set; } + + public virtual BlogPost BlogPost { get; set; } = null!; +} diff --git a/src/CMSMicroservice.Domain/Entities/Blog/BlogPostTag.cs b/src/CMSMicroservice.Domain/Entities/Blog/BlogPostTag.cs new file mode 100644 index 0000000..5daa2e7 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/Blog/BlogPostTag.cs @@ -0,0 +1,16 @@ +using CMSMicroservice.Domain.Common; + +namespace CMSMicroservice.Domain.Entities.Blog; + +/// +/// جدول واسط بین مقاله و تگ (چند به چند) +/// از Tag موجود استفاده مجدد می‌شود +/// +public class BlogPostTag : BaseEntity +{ + public long BlogPostId { get; set; } + public long TagId { get; set; } + + public virtual BlogPost BlogPost { get; set; } = null!; + public virtual Tag Tag { get; set; } = null!; +} diff --git a/src/CMSMicroservice.Domain/Entities/Content/SitePage.cs b/src/CMSMicroservice.Domain/Entities/Content/SitePage.cs new file mode 100644 index 0000000..b2d59e2 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/Content/SitePage.cs @@ -0,0 +1,28 @@ +using CMSMicroservice.Domain.Common; + +namespace CMSMicroservice.Domain.Entities.Content; + +/// +/// صفحات دینامیک سایت (درباره ما، تماس با ما و ...) +/// محتوای هر صفحه از طریق پنل ادمین قابل ویرایش است +/// +public class SitePage : BaseAuditableEntity +{ + //کلید یکتا (about, contact, ...) + public string PageKey { get; set; } = string.Empty; + //عنوان صفحه + public string Title { get; set; } = string.Empty; + //توضیح متا برای SEO + public string? MetaDescription { get; set; } + //عنوان Hero + public string? HeroTitle { get; set; } + //زیرعنوان Hero + public string? HeroSubtitle { get; set; } + //مسیر تصویر Hero + public string? HeroImagePath { get; set; } + //فعال؟ + public bool IsActive { get; set; } = true; + + //بخش‌های صفحه + public virtual ICollection Sections { get; set; } = new List(); +} diff --git a/src/CMSMicroservice.Domain/Entities/Content/SitePageSection.cs b/src/CMSMicroservice.Domain/Entities/Content/SitePageSection.cs new file mode 100644 index 0000000..cde18b1 --- /dev/null +++ b/src/CMSMicroservice.Domain/Entities/Content/SitePageSection.cs @@ -0,0 +1,34 @@ +using CMSMicroservice.Domain.Common; + +namespace CMSMicroservice.Domain.Entities.Content; + +/// +/// بخش‌های یک صفحه دینامیک +/// هر صفحه می‌تواند N بخش با ترتیب نمایش داشته باشد +/// +public class SitePageSection : BaseAuditableEntity +{ + public long SitePageId { get; set; } + //کلید بخش (mission, vision, team-member-1, ...) + public string SectionKey { get; set; } = string.Empty; + //عنوان بخش + public string Title { get; set; } = string.Empty; + //زیرعنوان + public string? Subtitle { get; set; } + //محتوای HTML + public string? HtmlContent { get; set; } + //نام آیکون Material + public string? IconName { get; set; } + //مسیر تصویر + public string? ImagePath { get; set; } + //مسیر تامبنیل + public string? ImageThumbnailPath { get; set; } + //ترتیب نمایش + public int SortOrder { get; set; } + //فعال؟ + public bool IsActive { get; set; } = true; + //داده اضافی JSON (برای فیلدهای انعطاف‌پذیر) + public string? ExtraData { get; set; } + + public virtual SitePage SitePage { get; set; } = null!; +} diff --git a/src/CMSMicroservice.Domain/Enums/BlogPostStatus.cs b/src/CMSMicroservice.Domain/Enums/BlogPostStatus.cs new file mode 100644 index 0000000..01ad0a5 --- /dev/null +++ b/src/CMSMicroservice.Domain/Enums/BlogPostStatus.cs @@ -0,0 +1,9 @@ +namespace CMSMicroservice.Domain.Enums; + +public enum BlogPostStatus +{ + Draft = 0, + Published = 1, + Scheduled = 2, + Archived = 3 +} diff --git a/src/CMSMicroservice.Domain/Events/OtpTokenEvents/CreateNewOtpTokenEvent.cs b/src/CMSMicroservice.Domain/Events/OtpTokenEvents/CreateNewOtpTokenEvent.cs index 59dfc91..ca32771 100644 --- a/src/CMSMicroservice.Domain/Events/OtpTokenEvents/CreateNewOtpTokenEvent.cs +++ b/src/CMSMicroservice.Domain/Events/OtpTokenEvents/CreateNewOtpTokenEvent.cs @@ -1,10 +1,11 @@ namespace CMSMicroservice.Domain.Events; public class CreateNewOtpTokenEvent : BaseEvent { - public CreateNewOtpTokenEvent(OtpToken item, string plainCode) + public CreateNewOtpTokenEvent(OtpToken item, string plainCode, string? signGuid = null) { Item = item; PlainCode = plainCode; + SignGuid = signGuid; } public OtpToken Item { get; } @@ -12,4 +13,8 @@ public class CreateNewOtpTokenEvent : BaseEvent /// کد OTP به صورت plain text برای ارسال SMS /// public string PlainCode { get; } + /// + /// شناسه GUID قرارداد (فقط برای امضای قرارداد) + /// + public string? SignGuid { get; } } diff --git a/src/CMSMicroservice.Infrastructure/ConfigureServices.cs b/src/CMSMicroservice.Infrastructure/ConfigureServices.cs index e4cfa0f..c6cb923 100644 --- a/src/CMSMicroservice.Infrastructure/ConfigureServices.cs +++ b/src/CMSMicroservice.Infrastructure/ConfigureServices.cs @@ -37,7 +37,8 @@ public static class ConfigureServices services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + // Local file manager — files are saved to wwwroot/uploads/ on CMS disk + services.AddSingleton(); services.AddScoped(); // Daya Loan API Service - قابل تغییر بین Mock و Real @@ -73,19 +74,31 @@ public static class ConfigureServices }); } - // Payment Gateway Service - فقط Daya (درگاه اینترنتی از Gateway میاد نه CMS) - var useRealPaymentGateway = configuration.GetValue("UseRealPaymentGateway", false); + // Payment Gateway Service - Multi-Provider Architecture + // پشتیبانی از درگاه‌های مختلف: ZarinPal, Daya, PYMS, Mock + var paymentProvider = configuration.GetValue("PaymentProvider", "Mock")?.ToLowerInvariant(); - if (useRealPaymentGateway) + switch (paymentProvider) { - // فقط Daya برای پرداخت به کاربران (Payout) - services.AddHttpClient() - .SetHandlerLifetime(TimeSpan.FromMinutes(5)); - } - else - { - // Mock برای Development و Testing - services.AddScoped(); + case "zarinpal": + services.AddHttpClient() + .SetHandlerLifetime(TimeSpan.FromMinutes(5)); + break; + + case "daya": + services.AddHttpClient() + .SetHandlerLifetime(TimeSpan.FromMinutes(5)); + break; + + case "pyms": + // PYMS (Payment Microservice) — ارتباط gRPC با سرویس پرداخت مستقل + services.AddSingleton(); + break; + + case "mock": + default: + services.AddScoped(); + break; } services.AddScoped(p => p.GetRequiredService()); diff --git a/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs b/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs index 02e3f58..3d35303 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/ApplicationDbContext.cs @@ -2,6 +2,8 @@ using System.Reflection; using CMSMicroservice.Application.Common.Interfaces; using Microsoft.EntityFrameworkCore.Diagnostics; using CMSMicroservice.Domain.Entities; +using CMSMicroservice.Domain.Entities.Blog; +using CMSMicroservice.Domain.Entities.Content; using CMSMicroservice.Domain.Entities.Payment; using CMSMicroservice.Domain.Entities.Geography; using CMSMicroservice.Domain.Entities.Order; @@ -138,4 +140,15 @@ public class ApplicationDbContext : DbContext, IApplicationDbContext public DbSet Warehouses => Set(); public DbSet InventoryItems => Set(); public DbSet StockMovements => Set(); + + // ============= Blog DbSets ============= + public DbSet BlogPosts => Set(); + public DbSet BlogCategories => Set(); + public DbSet BlogPostCategories => Set(); + public DbSet BlogPostTags => Set(); + public DbSet BlogPostImages => Set(); + + // ============= Content Management DbSets ============= + public DbSet SitePages => Set(); + public DbSet SitePageSections => Set(); } diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogCategoryConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogCategoryConfiguration.cs new file mode 100644 index 0000000..f5d9aa8 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogCategoryConfiguration.cs @@ -0,0 +1,25 @@ +using CMSMicroservice.Domain.Entities.Blog; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Blog; + +public class BlogCategoryConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("BlogCategories"); + builder.HasKey(x => x.Id); + + builder.Property(x => x.Title).IsRequired().HasMaxLength(100); + builder.Property(x => x.Slug).IsRequired().HasMaxLength(100); + builder.Property(x => x.Description).HasMaxLength(500); + builder.Property(x => x.IconName).HasMaxLength(100); + builder.Property(x => x.SortOrder).IsRequired().HasDefaultValue(0); + builder.Property(x => x.IsActive).IsRequired().HasDefaultValue(true); + + // Indexes + builder.HasIndex(x => x.Slug).IsUnique().HasDatabaseName("IX_BlogCategories_Slug"); + builder.HasIndex(x => x.IsActive).HasDatabaseName("IX_BlogCategories_IsActive"); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostCategoryConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostCategoryConfiguration.cs new file mode 100644 index 0000000..d701021 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostCategoryConfiguration.cs @@ -0,0 +1,22 @@ +using CMSMicroservice.Domain.Entities.Blog; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Blog; + +public class BlogPostCategoryConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("BlogPostCategories"); + builder.HasKey(e => e.Id); + + builder.HasOne(d => d.BlogPost) + .WithMany(p => p.BlogPostCategories) + .HasForeignKey(d => d.BlogPostId); + + builder.HasOne(d => d.BlogCategory) + .WithMany(p => p.BlogPostCategories) + .HasForeignKey(d => d.BlogCategoryId); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostConfiguration.cs new file mode 100644 index 0000000..d13e6a9 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostConfiguration.cs @@ -0,0 +1,35 @@ +using CMSMicroservice.Domain.Entities.Blog; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Blog; + +public class BlogPostConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("BlogPosts"); + builder.HasKey(x => x.Id); + + builder.Property(x => x.Title).IsRequired().HasMaxLength(200); + builder.Property(x => x.Slug).IsRequired().HasMaxLength(200); + builder.Property(x => x.Summary).HasMaxLength(500); + builder.Property(x => x.HtmlContent).IsRequired(); + builder.Property(x => x.FeaturedImagePath); + builder.Property(x => x.FeaturedImageThumbnailPath); + builder.Property(x => x.Status).IsRequired(); + builder.Property(x => x.ViewCount).IsRequired().HasDefaultValue(0); + builder.Property(x => x.AuthorUserId).IsRequired(); + builder.Property(x => x.IsFeatured).IsRequired().HasDefaultValue(false); + builder.Property(x => x.SortOrder).IsRequired().HasDefaultValue(0); + + // Indexes + builder.HasIndex(x => x.Slug).IsUnique().HasDatabaseName("IX_BlogPosts_Slug"); + builder.HasIndex(x => x.Status).HasDatabaseName("IX_BlogPosts_Status"); + builder.HasIndex(x => x.PublishedAt).HasDatabaseName("IX_BlogPosts_PublishedAt"); + builder.HasIndex(x => x.IsFeatured).HasDatabaseName("IX_BlogPosts_IsFeatured"); + builder.HasIndex(x => x.AuthorUserId).HasDatabaseName("IX_BlogPosts_AuthorUserId"); + builder.HasIndex(x => new { x.Status, x.PublishedAt }) + .HasDatabaseName("IX_BlogPosts_Status_PublishedAt"); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostImageConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostImageConfiguration.cs new file mode 100644 index 0000000..0f33690 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostImageConfiguration.cs @@ -0,0 +1,25 @@ +using CMSMicroservice.Domain.Entities.Blog; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Blog; + +public class BlogPostImageConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("BlogPostImages"); + builder.HasKey(x => x.Id); + + builder.Property(x => x.BlogPostId).IsRequired(); + builder.Property(x => x.ImagePath).IsRequired(); + builder.Property(x => x.ThumbnailPath).IsRequired(); + builder.Property(x => x.AltText).HasMaxLength(200); + builder.Property(x => x.Caption).HasMaxLength(300); + builder.Property(x => x.SortOrder).IsRequired().HasDefaultValue(0); + + builder.HasOne(d => d.BlogPost) + .WithMany(p => p.BlogPostImages) + .HasForeignKey(d => d.BlogPostId); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostTagConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostTagConfiguration.cs new file mode 100644 index 0000000..c369842 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Blog/BlogPostTagConfiguration.cs @@ -0,0 +1,22 @@ +using CMSMicroservice.Domain.Entities.Blog; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Blog; + +public class BlogPostTagConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("BlogPostTags"); + builder.HasKey(e => e.Id); + + builder.HasOne(d => d.BlogPost) + .WithMany(p => p.BlogPostTags) + .HasForeignKey(d => d.BlogPostId); + + builder.HasOne(d => d.Tag) + .WithMany() + .HasForeignKey(d => d.TagId); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Content/SitePageConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Content/SitePageConfiguration.cs new file mode 100644 index 0000000..38e012e --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Content/SitePageConfiguration.cs @@ -0,0 +1,25 @@ +using CMSMicroservice.Domain.Entities.Content; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Content; + +public class SitePageConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("SitePages"); + builder.HasKey(x => x.Id); + + builder.Property(x => x.PageKey).IsRequired().HasMaxLength(50); + builder.Property(x => x.Title).IsRequired().HasMaxLength(200); + builder.Property(x => x.MetaDescription).HasMaxLength(300); + builder.Property(x => x.HeroTitle).HasMaxLength(200); + builder.Property(x => x.HeroSubtitle).HasMaxLength(500); + builder.Property(x => x.HeroImagePath); + builder.Property(x => x.IsActive).IsRequired().HasDefaultValue(true); + + // Indexes + builder.HasIndex(x => x.PageKey).IsUnique().HasDatabaseName("IX_SitePages_PageKey"); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Content/SitePageSectionConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Content/SitePageSectionConfiguration.cs new file mode 100644 index 0000000..87924cb --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/Content/SitePageSectionConfiguration.cs @@ -0,0 +1,32 @@ +using CMSMicroservice.Domain.Entities.Content; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace CMSMicroservice.Infrastructure.Persistence.Configurations.Content; + +public class SitePageSectionConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("SitePageSections"); + builder.HasKey(x => x.Id); + + builder.Property(x => x.SitePageId).IsRequired(); + builder.Property(x => x.SectionKey).IsRequired().HasMaxLength(100); + builder.Property(x => x.Title).IsRequired().HasMaxLength(200); + builder.Property(x => x.Subtitle).HasMaxLength(300); + builder.Property(x => x.IconName).HasMaxLength(100); + builder.Property(x => x.ImagePath); + builder.Property(x => x.ImageThumbnailPath); + builder.Property(x => x.SortOrder).IsRequired().HasDefaultValue(0); + builder.Property(x => x.IsActive).IsRequired().HasDefaultValue(true); + + builder.HasOne(d => d.SitePage) + .WithMany(p => p.Sections) + .HasForeignKey(d => d.SitePageId); + + // Indexes + builder.HasIndex(x => new { x.SitePageId, x.SectionKey }) + .HasDatabaseName("IX_SitePageSections_PageId_SectionKey"); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountCategoryConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountCategoryConfiguration.cs index d8c919e..4cef90c 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountCategoryConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountCategoryConfiguration.cs @@ -28,8 +28,7 @@ public class DiscountCategoryConfiguration : IEntityTypeConfiguration entity.Description) .HasMaxLength(1000); - builder.Property(entity => entity.ImagePath) - .HasMaxLength(500); + builder.Property(entity => entity.ImagePath); builder.Property(entity => entity.IsActive) .IsRequired() diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountProductConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountProductConfiguration.cs index dae4d65..1d6b473 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountProductConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountProductConfiguration.cs @@ -36,12 +36,10 @@ public class DiscountProductConfiguration : IEntityTypeConfiguration entity.ImagePath) - .IsRequired() - .HasMaxLength(500); + .IsRequired(); builder.Property(entity => entity.ThumbnailPath) - .IsRequired() - .HasMaxLength(500); + .IsRequired(); builder.Property(entity => entity.IsActive) .IsRequired() diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountProductImageConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountProductImageConfiguration.cs index 2d34b72..1219b7b 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountProductImageConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/DiscountShop/DiscountProductImageConfiguration.cs @@ -19,11 +19,9 @@ public class DiscountProductImageConfiguration : IEntityTypeConfiguration x.ImagePath) - .IsRequired() - .HasMaxLength(500); + .IsRequired(); - builder.Property(x => x.ThumbnailPath) - .HasMaxLength(500); + builder.Property(x => x.ThumbnailPath); builder.HasOne(x => x.DiscountProduct) .WithMany(p => p.Images) diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ManualPaymentConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ManualPaymentConfiguration.cs index b93e304..ddc5b58 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ManualPaymentConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ManualPaymentConfiguration.cs @@ -8,6 +8,7 @@ public class ManualPaymentConfiguration : IEntityTypeConfiguration builder) { + builder.HasQueryFilter(p => !p.IsDeleted); builder.ToTable("ManualPayments"); builder.HasKey(x => x.Id); diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/OrderVATConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/OrderVATConfiguration.cs index 5d98f86..b7e76ee 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/OrderVATConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/OrderVATConfiguration.cs @@ -8,6 +8,7 @@ public class OrderVATConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { + builder.HasQueryFilter(p => !p.IsDeleted); builder.ToTable("OrderVATs"); builder.HasKey(x => x.Id); @@ -37,7 +38,7 @@ public class OrderVATConfiguration : IEntityTypeConfiguration // Foreign Key builder.HasOne(x => x.Order) - .WithOne() + .WithOne(x => x.OrderVAT) .HasForeignKey(x => x.OrderId) .OnDelete(DeleteBehavior.Restrict); diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ProductCategoryConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ProductCategoryConfiguration.cs index ff87209..6906551 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ProductCategoryConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ProductCategoryConfiguration.cs @@ -7,6 +7,7 @@ public class ProductCategoryConfiguration : IEntityTypeConfiguration builder) { + builder.HasQueryFilter(p => !p.IsDeleted); builder.ToTable("ProductCategories", "CMS"); builder.HasKey(e => e.Id); diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ProductTagConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ProductTagConfiguration.cs index 4c82748..93fa38e 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ProductTagConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/ProductTagConfiguration.cs @@ -7,6 +7,7 @@ public class ProductTagConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { + builder.HasQueryFilter(p => !p.IsDeleted); builder.ToTable("ProductTags", "CMS"); builder.HasKey(e => e.Id); diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PublicMessageConfiguration.cs b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PublicMessageConfiguration.cs index c4906ce..1513bc9 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PublicMessageConfiguration.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Configurations/PublicMessageConfiguration.cs @@ -8,6 +8,7 @@ public class PublicMessageConfiguration : IEntityTypeConfiguration builder) { + builder.HasQueryFilter(p => !p.IsDeleted); builder.ToTable("PublicMessages"); builder.HasKey(x => x.Id); diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260210232742_AddBlogAndContentEntities.Designer.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260210232742_AddBlogAndContentEntities.Designer.cs new file mode 100644 index 0000000..0bb9c60 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260210232742_AddBlogAndContentEntities.Designer.cs @@ -0,0 +1,4431 @@ +// +using System; +using CMSMicroservice.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CMSMicroservice.Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260210232742_AddBlogAndContentEntities")] + partial class AddBlogAndContentEntities + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("CMS") + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IconName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_BlogCategories_IsActive"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("IX_BlogCategories_Slug"); + + b.ToTable("BlogCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPost", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AuthorUserId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FeaturedImagePath") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("FeaturedImageThumbnailPath") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HtmlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsFeatured") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PublishedAt") + .HasColumnType("datetime2"); + + b.Property("ScheduledPublishAt") + .HasColumnType("datetime2"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Summary") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ViewCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("AuthorUserId") + .HasDatabaseName("IX_BlogPosts_AuthorUserId"); + + b.HasIndex("IsFeatured") + .HasDatabaseName("IX_BlogPosts_IsFeatured"); + + b.HasIndex("PublishedAt") + .HasDatabaseName("IX_BlogPosts_PublishedAt"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("IX_BlogPosts_Slug"); + + b.HasIndex("Status") + .HasDatabaseName("IX_BlogPosts_Status"); + + b.HasIndex("Status", "PublishedAt") + .HasDatabaseName("IX_BlogPosts_Status_PublishedAt"); + + b.ToTable("BlogPosts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BlogCategoryId") + .HasColumnType("bigint"); + + b.Property("BlogPostId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("BlogCategoryId"); + + b.HasIndex("BlogPostId"); + + b.ToTable("BlogPostCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AltText") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("BlogPostId") + .HasColumnType("bigint"); + + b.Property("Caption") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ThumbnailPath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.HasKey("Id"); + + b.HasIndex("BlogPostId"); + + b.ToTable("BlogPostImages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BlogPostId") + .HasColumnType("bigint"); + + b.Property("TagId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("BlogPostId"); + + b.HasIndex("TagId"); + + b.ToTable("BlogPostTags", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ParentId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("Categories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("IsActive", "SortOrder") + .HasDatabaseName("IX_ClubFeature_IsActive_SortOrder"); + + b.ToTable("ClubFeatures", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembership", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActivatedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("GiftValue") + .HasColumnType("bigint"); + + b.Property("InitialContribution") + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("TotalEarned") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_ClubMembership_IsActive"); + + b.HasIndex("UserId") + .IsUnique() + .HasDatabaseName("IX_ClubMembership_UserId"); + + b.ToTable("ClubMemberships", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.UserClubFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClubFeatureId") + .HasColumnType("bigint"); + + b.Property("ClubMembershipId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ClubFeatureId"); + + b.HasIndex("ClubMembershipId") + .HasDatabaseName("IX_UserClubFeature_ClubMembershipId"); + + b.HasIndex("UserId", "ClubFeatureId") + .IsUnique() + .HasDatabaseName("IX_UserClubFeature_UserId_ClubFeatureId"); + + b.ToTable("UserClubFeatures", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BalancesEarned") + .HasColumnType("int"); + + b.Property("BankReferenceId") + .HasColumnType("nvarchar(max)"); + + b.Property("BankTrackingCode") + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IbanNumber") + .HasMaxLength(26) + .HasColumnType("nvarchar(26)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PaidAt") + .HasColumnType("datetime2"); + + b.Property("PaymentFailureReason") + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessedAt") + .HasColumnType("datetime2"); + + b.Property("ProcessedBy") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RejectionReason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("ValuePerBalance") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.Property("WeeklyPoolId") + .HasColumnType("bigint"); + + b.Property("WithdrawalMethod") + .HasColumnType("int"); + + b.Property("WithdrawnAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("Status") + .HasDatabaseName("IX_UserCommissionPayout_Status"); + + b.HasIndex("WeekDefinitionId"); + + b.HasIndex("WeeklyPoolId") + .HasDatabaseName("IX_UserCommissionPayout_WeeklyPoolId"); + + b.HasIndex("UserId", "WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_UserCommissionPayout_UserId_WeekDefinitionId"); + + b.ToTable("UserCommissionPayouts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CalculatedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsCalculated") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("TotalBalances") + .HasColumnType("int"); + + b.Property("TotalPoolAmount") + .HasColumnType("bigint"); + + b.Property("ValuePerBalance") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("IsCalculated") + .HasDatabaseName("IX_WeeklyCommissionPool_IsCalculated"); + + b.HasIndex("WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_WeeklyCommissionPool_WeekDefinitionId"); + + b.ToTable("WeeklyCommissionPools", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WorkerExecutionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Details") + .HasColumnType("nvarchar(max)"); + + b.Property("DurationMs") + .HasColumnType("bigint"); + + b.Property("ErrorCount") + .HasColumnType("int"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("ErrorStackTrace") + .HasColumnType("nvarchar(max)"); + + b.Property("ExecutionId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessedCount") + .HasColumnType("int"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("StartedAt"); + + b.HasIndex("Status"); + + b.HasIndex("WeekDefinitionId"); + + b.ToTable("WorkerExecutionLogs", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Configuration.AppVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AppName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CurrentVersion") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MinRequiredVersion") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseNotes") + .HasColumnType("nvarchar(max)"); + + b.Property("RequiresFullCacheClear") + .HasColumnType("bit"); + + b.Property("UpdateMessage") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("AppVersions", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("HeroImagePath") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HeroSubtitle") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HeroTitle") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MetaDescription") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("PageKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("PageKey") + .IsUnique() + .HasDatabaseName("IX_SitePages_PageKey"); + + b.ToTable("SitePages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExtraData") + .HasColumnType("nvarchar(max)"); + + b.Property("HtmlContent") + .HasColumnType("nvarchar(max)"); + + b.Property("IconName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ImagePath") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ImageThumbnailPath") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SectionKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SitePageId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Subtitle") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("SitePageId", "SectionKey") + .HasDatabaseName("IX_SitePageSections_PageId_SectionKey"); + + b.ToTable("SitePageSections", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Contract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("HtmlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Contracts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DayaLoanContract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ContractNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsProcessed") + .HasColumnType("bit"); + + b.Property("LastCheckDate") + .HasColumnType("datetime2"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NationalCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessedDate") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserId"); + + b.ToTable("DayaLoanContracts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("ImagePath") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ParentCategoryId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("ParentCategoryId"); + + b.ToTable("DiscountCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeliveryDescription") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("DeliveryStatus") + .HasColumnType("int"); + + b.Property("DiscountBalanceUsed") + .HasColumnType("bigint"); + + b.Property("GatewayAmountPaid") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PaymentDate") + .HasColumnType("datetime2"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("bigint"); + + b.Property("TrackingCode") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserAddressId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("VatAmount") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserAddressId"); + + b.HasIndex("UserId"); + + b.ToTable("DiscountOrders", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrderDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountAmount") + .HasColumnType("bigint"); + + b.Property("DiscountOrderId") + .HasColumnType("bigint"); + + b.Property("DiscountPercentUsed") + .HasColumnType("int"); + + b.Property("FinalPrice") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UnitPrice") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DiscountOrderId"); + + b.HasIndex("ProductId"); + + b.ToTable("DiscountOrderDetails", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FullInformation") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("ImagePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxDiscountPercent") + .HasColumnType("int"); + + b.Property("Price") + .HasColumnType("bigint"); + + b.Property("Rate") + .HasColumnType("int"); + + b.Property("RemainingCount") + .HasColumnType("int"); + + b.Property("SaleCount") + .HasColumnType("int"); + + b.Property("ShortInfomation") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ThumbnailPath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ViewCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("DiscountProducts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ProductId", "CategoryId") + .IsUnique(); + + b.ToTable("DiscountProductCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AltText") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountProductId") + .HasColumnType("bigint"); + + b.Property("ImagePath") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("ThumbnailPath") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Title") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("DiscountProductId"); + + b.HasIndex("DiscountProductId", "SortOrder"); + + b.ToTable("DiscountProductImages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountShoppingCart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId", "ProductId") + .IsUnique(); + + b.ToTable("DiscountShoppingCarts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.FactorDetails", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsChangePrice") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UnitDiscount") + .HasColumnType("int"); + + b.Property("UnitDiscountPrice") + .HasColumnType("bigint"); + + b.Property("UnitPrice") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("ProductId"); + + b.ToTable("FactorDetails", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.City", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExternalId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Latitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Longitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Native") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasDefaultValue(""); + + b.Property("StateId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("StateId") + .HasDatabaseName("IX_Cities_StateId"); + + b.ToTable("Cities", "GMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.Country", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Capital") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("CurrencyName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CurrencySymbol") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Emoji") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("EmojiU") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ExternalId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("Iso2") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)"); + + b.Property("Iso3") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("nvarchar(3)"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Latitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Longitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Native") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("NumericCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("PhoneCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Region") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Subregion") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Tld") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.HasKey("Id"); + + b.ToTable("Countries", "GMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.State", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CountryId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExternalId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Latitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Longitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Native") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasDefaultValue(""); + + b.Property("StateCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("CountryId") + .HasDatabaseName("IX_States_CountryId"); + + b.ToTable("States", "GMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("ClubMembershipId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewInitialContribution") + .HasColumnType("bigint"); + + b.Property("NewIsActive") + .HasColumnType("bit"); + + b.Property("OldInitialContribution") + .HasColumnType("bigint"); + + b.Property("OldIsActive") + .HasColumnType("bit"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_ClubMembershipHistory_Action"); + + b.HasIndex("ClubMembershipId") + .HasDatabaseName("IX_ClubMembershipHistory_ClubMembershipId"); + + b.HasIndex("UserId", "Created") + .HasDatabaseName("IX_ClubMembershipHistory_UserId_Created"); + + b.ToTable("ClubMembershipHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.CommissionPayoutHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("AmountAfter") + .HasColumnType("bigint"); + + b.Property("AmountBefore") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewStatus") + .HasColumnType("int"); + + b.Property("OldStatus") + .HasColumnType("int"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserCommissionPayoutId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_CommissionPayoutHistory_Action"); + + b.HasIndex("UserCommissionPayoutId") + .HasDatabaseName("IX_CommissionPayoutHistory_PayoutId"); + + b.HasIndex("WeekDefinitionId") + .HasDatabaseName("IX_CommissionPayoutHistory_WeekDefinitionId"); + + b.HasIndex("UserId", "Created") + .HasDatabaseName("IX_CommissionPayoutHistory_UserId_Created"); + + b.ToTable("CommissionPayoutHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.NetworkMembershipHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewLegPosition") + .HasColumnType("int"); + + b.Property("NewParentId") + .HasColumnType("bigint"); + + b.Property("OldLegPosition") + .HasColumnType("int"); + + b.Property("OldParentId") + .HasColumnType("bigint"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_NetworkMembershipHistory_Action"); + + b.HasIndex("UserId", "Created") + .HasDatabaseName("IX_NetworkMembershipHistory_UserId_Created"); + + b.ToTable("NetworkMembershipHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.InventoryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountProductId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LastRestockedAt") + .HasColumnType("datetime2"); + + b.Property("LastSoldAt") + .HasColumnType("datetime2"); + + b.Property("LowStockThreshold") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(10); + + b.Property("MaxStockLevel") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1000); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("ProductType") + .HasColumnType("int"); + + b.Property("Quantity") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ReorderPoint") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(5); + + b.Property("ReservedQuantity") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("WarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(1L); + + b.HasKey("Id"); + + b.HasIndex("DiscountProductId") + .HasDatabaseName("IX_InventoryItems_DiscountProductId"); + + b.HasIndex("ProductId") + .HasDatabaseName("IX_InventoryItems_ProductId"); + + b.HasIndex("WarehouseId") + .HasDatabaseName("IX_InventoryItems_WarehouseId"); + + b.HasIndex("ProductType", "Quantity") + .HasDatabaseName("IX_InventoryItems_ProductType_Quantity"); + + b.ToTable("InventoryItems", "CMS", t => + { + t.HasCheckConstraint("CK_InventoryItem_ProductReference", "(ProductId IS NOT NULL AND DiscountProductId IS NULL) OR (ProductId IS NULL AND DiscountProductId IS NOT NULL)"); + + t.HasCheckConstraint("CK_InventoryItem_ProductType_Match", "(ProductType = 1 AND ProductId IS NOT NULL) OR (ProductType = 2 AND DiscountProductId IS NOT NULL)"); + + t.HasCheckConstraint("CK_InventoryItem_Quantity_NonNegative", "Quantity >= 0"); + + t.HasCheckConstraint("CK_InventoryItem_ReservedQuantity_LessOrEqualQuantity", "ReservedQuantity <= Quantity"); + + t.HasCheckConstraint("CK_InventoryItem_ReservedQuantity_NonNegative", "ReservedQuantity >= 0"); + }); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Network.NetworkWeeklyBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CalculatedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FlushedPerSide") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsExpired") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LeftLegBalances") + .HasColumnType("int"); + + b.Property("LeftLegCarryover") + .HasColumnType("int"); + + b.Property("LeftLegNewMembers") + .HasColumnType("int"); + + b.Property("LeftLegRemainder") + .HasColumnType("int"); + + b.Property("LeftLegTotal") + .HasColumnType("int"); + + b.Property("RightLegBalances") + .HasColumnType("int"); + + b.Property("RightLegCarryover") + .HasColumnType("int"); + + b.Property("RightLegNewMembers") + .HasColumnType("int"); + + b.Property("RightLegRemainder") + .HasColumnType("int"); + + b.Property("RightLegTotal") + .HasColumnType("int"); + + b.Property("SubordinateBalances") + .HasColumnType("int"); + + b.Property("TotalBalances") + .HasColumnType("int"); + + b.Property("TotalFlushed") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.Property("WeeklyPoolContribution") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("IsExpired") + .HasDatabaseName("IX_NetworkWeeklyBalance_IsExpired"); + + b.HasIndex("WeekDefinitionId") + .HasDatabaseName("IX_NetworkWeeklyBalance_WeekDefinitionId"); + + b.HasIndex("UserId", "WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_NetworkWeeklyBalance_UserId_WeekDefinitionId"); + + b.ToTable("NetworkWeeklyBalances", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BaseAmount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsPaid") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("PaidAt") + .HasColumnType("datetime2"); + + b.Property("TotalAmount") + .HasColumnType("bigint"); + + b.Property("VATAmount") + .HasColumnType("bigint"); + + b.Property("VATRate") + .HasColumnType("decimal(5,4)"); + + b.HasKey("Id"); + + b.HasIndex("Created") + .HasDatabaseName("IX_OrderVATs_Created"); + + b.HasIndex("IsPaid") + .HasDatabaseName("IX_OrderVATs_IsPaid"); + + b.HasIndex("OrderId") + .IsUnique() + .HasDatabaseName("IX_OrderVATs_OrderId"); + + b.ToTable("OrderVATs", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.OtpToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Attempts") + .HasColumnType("int"); + + b.Property("CodeHash") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsUsed") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Mobile") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Purpose") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("OtpTokens", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Package", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("bigint"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Packages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Payment.ManualPayment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("ApprovedAt") + .HasColumnType("datetime2"); + + b.Property("ApprovedBy") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("ImageDocumentId") + .HasColumnType("bigint"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ReferenceNumber") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("RejectionReason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("RequestedBy") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ApprovedBy"); + + b.HasIndex("Created"); + + b.HasIndex("RequestedBy"); + + b.HasIndex("Status"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Status"); + + b.ToTable("ManualPayments", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClubDiscountPercent") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Discount") + .HasColumnType("int"); + + b.Property("FullInformation") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsClubExclusive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("bigint"); + + b.Property("Rate") + .HasColumnType("int"); + + b.Property("RemainingCount") + .HasColumnType("int"); + + b.Property("SaleCount") + .HasColumnType("int"); + + b.Property("ShortInfomation") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ThumbnailPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ViewCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("IsClubExclusive") + .HasDatabaseName("IX_Products_IsClubExclusive"); + + b.ToTable("Products", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ProductId"); + + b.ToTable("ProductCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductGallery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("ProductImageId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("ProductImageId"); + + b.ToTable("ProductGalleries", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImageThumbnailPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("ProductImages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("TagId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("TagId"); + + b.ToTable("ProductTags", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.PublicMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ArchivedAt") + .HasColumnType("datetime2"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedByUserId") + .HasColumnType("bigint"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsArchived") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LinkText") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LinkUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("PublishedAt") + .HasColumnType("datetime2"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("ViewCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId") + .HasDatabaseName("IX_PublicMessages_CreatedByUserId"); + + b.HasIndex("ExpiresAt") + .HasDatabaseName("IX_PublicMessages_ExpiresAt"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_PublicMessages_IsActive"); + + b.HasIndex("Priority") + .HasDatabaseName("IX_PublicMessages_Priority"); + + b.HasIndex("StartsAt") + .HasDatabaseName("IX_PublicMessages_StartsAt"); + + b.HasIndex("Type") + .HasDatabaseName("IX_PublicMessages_Type"); + + b.HasIndex("IsActive", "ExpiresAt") + .HasDatabaseName("IX_PublicMessages_IsActive_ExpiresAt"); + + b.ToTable("PublicMessages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Roles", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.StockMovement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountOrderId") + .HasColumnType("bigint"); + + b.Property("InventoryItemId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MovementType") + .HasColumnType("int"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("PerformedByUserId") + .HasColumnType("bigint"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("QuantityAfter") + .HasColumnType("int"); + + b.Property("QuantityBefore") + .HasColumnType("int"); + + b.Property("ReferenceNumber") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Created") + .HasDatabaseName("IX_StockMovements_Created"); + + b.HasIndex("DiscountOrderId") + .HasDatabaseName("IX_StockMovements_DiscountOrderId") + .HasFilter("[DiscountOrderId] IS NOT NULL"); + + b.HasIndex("InventoryItemId") + .HasDatabaseName("IX_StockMovements_InventoryItemId"); + + b.HasIndex("MovementType") + .HasDatabaseName("IX_StockMovements_MovementType"); + + b.HasIndex("OrderId") + .HasDatabaseName("IX_StockMovements_OrderId") + .HasFilter("[OrderId] IS NOT NULL"); + + b.HasIndex("ReferenceNumber") + .HasDatabaseName("IX_StockMovements_ReferenceNumber") + .HasFilter("[ReferenceNumber] IS NOT NULL"); + + b.HasIndex("InventoryItemId", "MovementType", "Created") + .HasDatabaseName("IX_StockMovements_Item_Type_Date"); + + b.ToTable("StockMovements", "CMS", t => + { + t.HasCheckConstraint("CK_StockMovement_QuantityAfter_Calculation", "QuantityAfter = QuantityBefore + Quantity"); + + t.HasCheckConstraint("CK_StockMovement_QuantityAfter_NonNegative", "QuantityAfter >= 0"); + + t.HasCheckConstraint("CK_StockMovement_QuantityBefore_NonNegative", "QuantityBefore >= 0"); + }); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Tags", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Transaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PaymentDate") + .HasColumnType("datetime2"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("RefId") + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Transactions", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AvatarPath") + .HasColumnType("nvarchar(max)"); + + b.Property("BirthDate") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DayaCreditReceivedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("EmailNotifications") + .HasColumnType("bit"); + + b.Property("FirstName") + .HasColumnType("nvarchar(max)"); + + b.Property("HasReceivedDayaCredit") + .HasColumnType("bit"); + + b.Property("HashPassword") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsMobileVerified") + .HasColumnType("bit"); + + b.Property("IsRulesAccepted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .HasColumnType("nvarchar(max)"); + + b.Property("LegPosition") + .HasColumnType("int"); + + b.Property("Mobile") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("MobileVerifiedAt") + .HasColumnType("datetime2"); + + b.Property("NationalCode") + .HasColumnType("nvarchar(max)"); + + b.Property("NetworkParentId") + .HasColumnType("bigint"); + + b.Property("PackagePurchaseMethod") + .HasColumnType("int"); + + b.Property("PushNotifications") + .HasColumnType("bit"); + + b.Property("ReferralCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RulesAcceptedAt") + .HasColumnType("datetime2"); + + b.Property("SmsNotifications") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("LegPosition") + .HasDatabaseName("IX_User_LegPosition"); + + b.HasIndex("NetworkParentId") + .HasDatabaseName("IX_User_NetworkParentId"); + + b.ToTable("Users", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CityId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDefault") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserAddresses", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserCart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId"); + + b.ToTable("UserCarts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserContract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ContractId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SignGuid") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SignedPdfFile") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ContractId"); + + b.HasIndex("UserId"); + + b.ToTable("UserContracts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeliveryDescription") + .HasColumnType("nvarchar(max)"); + + b.Property("DeliveryStatus") + .HasColumnType("int"); + + b.Property("HasVAT") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderVATId") + .HasColumnType("bigint"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("PaymentDate") + .HasColumnType("datetime2"); + + b.Property("PaymentMethod") + .HasColumnType("int"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("TrackingCode") + .HasColumnType("nvarchar(max)"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserAddressId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("OrderVATId"); + + b.HasIndex("PackageId"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserAddressId"); + + b.HasIndex("UserId"); + + b.ToTable("UserOrders", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserPackagePurchase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("PurchasedAt") + .HasColumnType("datetime2"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("PackageId") + .HasDatabaseName("IX_UserPackagePurchase_PackageId"); + + b.HasIndex("PurchasedAt") + .HasDatabaseName("IX_UserPackagePurchase_PurchasedAt"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserId") + .HasDatabaseName("IX_UserPackagePurchase_UserId"); + + b.HasIndex("UserId", "PurchasedAt") + .HasDatabaseName("IX_UserPackagePurchase_UserId_PurchasedAt"); + + b.ToTable("UserPackagePurchases", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.HasIndex("UserId"); + + b.ToTable("UserRoles", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountBalance") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NetworkBalance") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserWallets", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWalletChangeLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChangeDiscountValue") + .HasColumnType("bigint"); + + b.Property("ChangeNerworkValue") + .HasColumnType("bigint"); + + b.Property("ChangeValue") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CurrentBalance") + .HasColumnType("bigint"); + + b.Property("CurrentDiscountBalance") + .HasColumnType("bigint"); + + b.Property("CurrentNetworkBalance") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsIncrease") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("RefrenceId") + .HasColumnType("bigint"); + + b.Property("WalletId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("WalletId"); + + b.ToTable("UserWalletChangeLogs", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Warehouse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Address") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDefault") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique() + .HasDatabaseName("IX_Warehouses_Code_Unique"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_Warehouses_IsActive"); + + b.HasIndex("IsDefault") + .HasDatabaseName("IX_Warehouses_IsDefault") + .HasFilter("[IsDefault] = 1"); + + b.ToTable("Warehouses", "CMS"); + + b.HasData( + new + { + Id = 1L, + Address = "تهران - انبار مرکزی فروشگاه", + Code = "WH-001", + Created = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + CreatedBy = "System", + IsActive = true, + IsDefault = true, + IsDeleted = false, + Name = "انبار اصلی" + }); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.WeekDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("GregorianWeekNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("GregorianYear") + .HasColumnType("int"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PersianWeekNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("PersianYear") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("WeekOrder") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GregorianWeekNumber") + .IsUnique() + .HasDatabaseName("IX_WeekDefinition_GregorianWeekNumber"); + + b.HasIndex("GregorianYear") + .HasDatabaseName("IX_WeekDefinition_GregorianYear"); + + b.HasIndex("PersianWeekNumber") + .HasDatabaseName("IX_WeekDefinition_PersianWeekNumber"); + + b.HasIndex("PersianYear") + .HasDatabaseName("IX_WeekDefinition_PersianYear"); + + b.HasIndex("StartDate") + .HasDatabaseName("IX_WeekDefinition_StartDate"); + + b.ToTable("WeekDefinitions", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogCategory", "BlogCategory") + .WithMany("BlogPostCategories") + .HasForeignKey("BlogCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost") + .WithMany("BlogPostCategories") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogCategory"); + + b.Navigation("BlogPost"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostImage", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost") + .WithMany("BlogPostImages") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogPost"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostTag", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost") + .WithMany("BlogPostTags") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogPost"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Category", "Parent") + .WithMany("Categories") + .HasForeignKey("ParentId"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembership", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithOne("ClubMembership") + .HasForeignKey("CMSMicroservice.Domain.Entities.Club.ClubMembership", "UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.UserClubFeature", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubFeature", "ClubFeature") + .WithMany("UserClubFeatures") + .HasForeignKey("ClubFeatureId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubMembership", "ClubMembership") + .WithMany("UserClubFeatures") + .HasForeignKey("ClubMembershipId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserClubFeatures") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ClubFeature"); + + b.Navigation("ClubMembership"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("CommissionPayouts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("UserCommissionPayouts") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", "WeeklyPool") + .WithMany("UserCommissionPayouts") + .HasForeignKey("WeeklyPoolId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("User"); + + b.Navigation("WeekDefinition"); + + b.Navigation("WeeklyPool"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("WeeklyCommissionPools") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WorkerExecutionLog", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("WorkerExecutionLogs") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSection", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Content.SitePage", "SitePage") + .WithMany("Sections") + .HasForeignKey("SitePageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SitePage"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DayaLoanContract", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId"); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("DayaLoanContracts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Transaction"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", "ParentCategory") + .WithMany("ChildCategories") + .HasForeignKey("ParentCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("ParentCategory"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.UserAddress", "UserAddress") + .WithMany() + .HasForeignKey("UserAddressId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("DiscountOrders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Transaction"); + + b.Navigation("User"); + + b.Navigation("UserAddress"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrderDetail", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", "DiscountOrder") + .WithMany("OrderDetails") + .HasForeignKey("DiscountOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "Product") + .WithMany("OrderDetails") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DiscountOrder"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", "Category") + .WithMany("ProductCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "Product") + .WithMany("ProductCategories") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductImage", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "DiscountProduct") + .WithMany("Images") + .HasForeignKey("DiscountProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DiscountProduct"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountShoppingCart", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "Product") + .WithMany("ShoppingCarts") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("DiscountShoppingCarts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.FactorDetails", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithMany("FactorDetails") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("FactorDetails") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.City", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Geography.State", "State") + .WithMany("Cities") + .HasForeignKey("StateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("State"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.State", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Geography.Country", "Country") + .WithMany("States") + .HasForeignKey("CountryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Country"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipHistory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubMembership", "ClubMembership") + .WithMany("ClubMembershipHistories") + .HasForeignKey("ClubMembershipId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ClubMembership"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.CommissionPayoutHistory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", "UserCommissionPayout") + .WithMany("CommissionPayoutHistories") + .HasForeignKey("UserCommissionPayoutId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("CommissionPayoutHistories") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("UserCommissionPayout"); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.InventoryItem", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "DiscountProduct") + .WithMany() + .HasForeignKey("DiscountProductId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("CMSMicroservice.Domain.Entities.Warehouse", "Warehouse") + .WithMany("InventoryItems") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DiscountProduct"); + + b.Navigation("Product"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Network.NetworkWeeklyBalance", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("NetworkWeeklyBalances") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("NetworkWeeklyBalances") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("User"); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithOne() + .HasForeignKey("CMSMicroservice.Domain.Entities.Order.OrderVAT", "OrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Payment.ManualPayment", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Transaction"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Category", "Category") + .WithMany("ProductCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("ProductCategories") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductGallery", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("ProductGalleries") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.ProductImage", "ProductImage") + .WithMany("ProductGalleries") + .HasForeignKey("ProductImageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("ProductImage"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductTag", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("ProductTags") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Tag", "Tag") + .WithMany("ProductTags") + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.StockMovement", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.InventoryItem", "InventoryItem") + .WithMany("StockMovements") + .HasForeignKey("InventoryItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("InventoryItem"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.User", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "NetworkParent") + .WithMany("NetworkChildren") + .HasForeignKey("NetworkParentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("NetworkParent"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserAddress", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserAddresses") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserCart", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("UserCarts") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserCarts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserContract", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Contract", "Contract") + .WithMany("UserContracts") + .HasForeignKey("ContractId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserContracts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Contract"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Order.OrderVAT", "OrderVAT") + .WithMany() + .HasForeignKey("OrderVATId"); + + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany("UserOrders") + .HasForeignKey("PackageId"); + + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany("UserOrders") + .HasForeignKey("TransactionId"); + + b.HasOne("CMSMicroservice.Domain.Entities.UserAddress", "UserAddress") + .WithMany("UserOrders") + .HasForeignKey("UserAddressId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserOrders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("OrderVAT"); + + b.Navigation("Package"); + + b.Navigation("Transaction"); + + b.Navigation("User"); + + b.Navigation("UserAddress"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserPackagePurchase", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithMany() + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Package"); + + b.Navigation("Transaction"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserRole", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Role", "Role") + .WithMany("UserRoles") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserRoles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserWallets") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWalletChangeLog", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserWallet", "Wallet") + .WithMany("UserWalletChangeLogs") + .HasForeignKey("WalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Wallet"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogCategory", b => + { + b.Navigation("BlogPostCategories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPost", b => + { + b.Navigation("BlogPostCategories"); + + b.Navigation("BlogPostImages"); + + b.Navigation("BlogPostTags"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b => + { + b.Navigation("Categories"); + + b.Navigation("ProductCategories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubFeature", b => + { + b.Navigation("UserClubFeatures"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembership", b => + { + b.Navigation("ClubMembershipHistories"); + + b.Navigation("UserClubFeatures"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", b => + { + b.Navigation("CommissionPayoutHistories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.Navigation("UserCommissionPayouts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePage", b => + { + b.Navigation("Sections"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Contract", b => + { + b.Navigation("UserContracts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", b => + { + b.Navigation("ChildCategories"); + + b.Navigation("ProductCategories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", b => + { + b.Navigation("OrderDetails"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", b => + { + b.Navigation("Images"); + + b.Navigation("OrderDetails"); + + b.Navigation("ProductCategories"); + + b.Navigation("ShoppingCarts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.Country", b => + { + b.Navigation("States"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.State", b => + { + b.Navigation("Cities"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.InventoryItem", b => + { + b.Navigation("StockMovements"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Package", b => + { + b.Navigation("UserOrders"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Product", b => + { + b.Navigation("FactorDetails"); + + b.Navigation("ProductCategories"); + + b.Navigation("ProductGalleries"); + + b.Navigation("ProductTags"); + + b.Navigation("UserCarts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductImage", b => + { + b.Navigation("ProductGalleries"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Role", b => + { + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Tag", b => + { + b.Navigation("ProductTags"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Transaction", b => + { + b.Navigation("UserOrders"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.User", b => + { + b.Navigation("ClubMembership"); + + b.Navigation("CommissionPayouts"); + + b.Navigation("DayaLoanContracts"); + + b.Navigation("DiscountOrders"); + + b.Navigation("DiscountShoppingCarts"); + + b.Navigation("NetworkChildren"); + + b.Navigation("NetworkWeeklyBalances"); + + b.Navigation("UserAddresses"); + + b.Navigation("UserCarts"); + + b.Navigation("UserClubFeatures"); + + b.Navigation("UserContracts"); + + b.Navigation("UserOrders"); + + b.Navigation("UserRoles"); + + b.Navigation("UserWallets"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserAddress", b => + { + b.Navigation("UserOrders"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => + { + b.Navigation("FactorDetails"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b => + { + b.Navigation("UserWalletChangeLogs"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Warehouse", b => + { + b.Navigation("InventoryItems"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.WeekDefinition", b => + { + b.Navigation("CommissionPayoutHistories"); + + b.Navigation("NetworkWeeklyBalances"); + + b.Navigation("UserCommissionPayouts"); + + b.Navigation("WeeklyCommissionPools"); + + b.Navigation("WorkerExecutionLogs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260210232742_AddBlogAndContentEntities.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260210232742_AddBlogAndContentEntities.cs new file mode 100644 index 0000000..29ee152 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260210232742_AddBlogAndContentEntities.cs @@ -0,0 +1,345 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CMSMicroservice.Infrastructure.Persistence.Migrations +{ + /// + public partial class AddBlogAndContentEntities : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "BlogCategories", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Title = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + Slug = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + Description = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + IconName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + SortOrder = table.Column(type: "int", nullable: false, defaultValue: 0), + IsActive = table.Column(type: "bit", nullable: false, defaultValue: true), + Created = table.Column(type: "datetime2", nullable: false), + CreatedBy = table.Column(type: "nvarchar(max)", nullable: true), + LastModified = table.Column(type: "datetime2", nullable: true), + LastModifiedBy = table.Column(type: "nvarchar(max)", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BlogCategories", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BlogPosts", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Title = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Slug = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Summary = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + HtmlContent = table.Column(type: "nvarchar(max)", nullable: false), + FeaturedImagePath = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + FeaturedImageThumbnailPath = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + Status = table.Column(type: "int", nullable: false), + PublishedAt = table.Column(type: "datetime2", nullable: true), + ScheduledPublishAt = table.Column(type: "datetime2", nullable: true), + ViewCount = table.Column(type: "int", nullable: false, defaultValue: 0), + AuthorUserId = table.Column(type: "bigint", nullable: false), + IsFeatured = table.Column(type: "bit", nullable: false, defaultValue: false), + SortOrder = table.Column(type: "int", nullable: false, defaultValue: 0), + Created = table.Column(type: "datetime2", nullable: false), + CreatedBy = table.Column(type: "nvarchar(max)", nullable: true), + LastModified = table.Column(type: "datetime2", nullable: true), + LastModifiedBy = table.Column(type: "nvarchar(max)", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BlogPosts", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "SitePages", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + PageKey = table.Column(type: "nvarchar(50)", maxLength: 50, nullable: false), + Title = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + MetaDescription = table.Column(type: "nvarchar(300)", maxLength: 300, nullable: true), + HeroTitle = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + HeroSubtitle = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + HeroImagePath = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + IsActive = table.Column(type: "bit", nullable: false, defaultValue: true), + Created = table.Column(type: "datetime2", nullable: false), + CreatedBy = table.Column(type: "nvarchar(max)", nullable: true), + LastModified = table.Column(type: "datetime2", nullable: true), + LastModifiedBy = table.Column(type: "nvarchar(max)", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SitePages", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "BlogPostCategories", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BlogPostId = table.Column(type: "bigint", nullable: false), + BlogCategoryId = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BlogPostCategories", x => x.Id); + table.ForeignKey( + name: "FK_BlogPostCategories_BlogCategories_BlogCategoryId", + column: x => x.BlogCategoryId, + principalSchema: "CMS", + principalTable: "BlogCategories", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BlogPostCategories_BlogPosts_BlogPostId", + column: x => x.BlogPostId, + principalSchema: "CMS", + principalTable: "BlogPosts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "BlogPostImages", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BlogPostId = table.Column(type: "bigint", nullable: false), + ImagePath = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + ThumbnailPath = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: false), + AltText = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: true), + Caption = table.Column(type: "nvarchar(300)", maxLength: 300, nullable: true), + SortOrder = table.Column(type: "int", nullable: false, defaultValue: 0), + Created = table.Column(type: "datetime2", nullable: false), + CreatedBy = table.Column(type: "nvarchar(max)", nullable: true), + LastModified = table.Column(type: "datetime2", nullable: true), + LastModifiedBy = table.Column(type: "nvarchar(max)", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BlogPostImages", x => x.Id); + table.ForeignKey( + name: "FK_BlogPostImages_BlogPosts_BlogPostId", + column: x => x.BlogPostId, + principalSchema: "CMS", + principalTable: "BlogPosts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "BlogPostTags", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + BlogPostId = table.Column(type: "bigint", nullable: false), + TagId = table.Column(type: "bigint", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BlogPostTags", x => x.Id); + table.ForeignKey( + name: "FK_BlogPostTags_BlogPosts_BlogPostId", + column: x => x.BlogPostId, + principalSchema: "CMS", + principalTable: "BlogPosts", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_BlogPostTags_Tags_TagId", + column: x => x.TagId, + principalSchema: "CMS", + principalTable: "Tags", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "SitePageSections", + schema: "CMS", + columns: table => new + { + Id = table.Column(type: "bigint", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + SitePageId = table.Column(type: "bigint", nullable: false), + SectionKey = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: false), + Title = table.Column(type: "nvarchar(200)", maxLength: 200, nullable: false), + Subtitle = table.Column(type: "nvarchar(300)", maxLength: 300, nullable: true), + HtmlContent = table.Column(type: "nvarchar(max)", nullable: true), + IconName = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), + ImagePath = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + ImageThumbnailPath = table.Column(type: "nvarchar(500)", maxLength: 500, nullable: true), + SortOrder = table.Column(type: "int", nullable: false, defaultValue: 0), + IsActive = table.Column(type: "bit", nullable: false, defaultValue: true), + ExtraData = table.Column(type: "nvarchar(max)", nullable: true), + Created = table.Column(type: "datetime2", nullable: false), + CreatedBy = table.Column(type: "nvarchar(max)", nullable: true), + LastModified = table.Column(type: "datetime2", nullable: true), + LastModifiedBy = table.Column(type: "nvarchar(max)", nullable: true), + IsDeleted = table.Column(type: "bit", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SitePageSections", x => x.Id); + table.ForeignKey( + name: "FK_SitePageSections_SitePages_SitePageId", + column: x => x.SitePageId, + principalSchema: "CMS", + principalTable: "SitePages", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_BlogCategories_IsActive", + schema: "CMS", + table: "BlogCategories", + column: "IsActive"); + + migrationBuilder.CreateIndex( + name: "IX_BlogCategories_Slug", + schema: "CMS", + table: "BlogCategories", + column: "Slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BlogPostCategories_BlogCategoryId", + schema: "CMS", + table: "BlogPostCategories", + column: "BlogCategoryId"); + + migrationBuilder.CreateIndex( + name: "IX_BlogPostCategories_BlogPostId", + schema: "CMS", + table: "BlogPostCategories", + column: "BlogPostId"); + + migrationBuilder.CreateIndex( + name: "IX_BlogPostImages_BlogPostId", + schema: "CMS", + table: "BlogPostImages", + column: "BlogPostId"); + + migrationBuilder.CreateIndex( + name: "IX_BlogPosts_AuthorUserId", + schema: "CMS", + table: "BlogPosts", + column: "AuthorUserId"); + + migrationBuilder.CreateIndex( + name: "IX_BlogPosts_IsFeatured", + schema: "CMS", + table: "BlogPosts", + column: "IsFeatured"); + + migrationBuilder.CreateIndex( + name: "IX_BlogPosts_PublishedAt", + schema: "CMS", + table: "BlogPosts", + column: "PublishedAt"); + + migrationBuilder.CreateIndex( + name: "IX_BlogPosts_Slug", + schema: "CMS", + table: "BlogPosts", + column: "Slug", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BlogPosts_Status", + schema: "CMS", + table: "BlogPosts", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_BlogPosts_Status_PublishedAt", + schema: "CMS", + table: "BlogPosts", + columns: new[] { "Status", "PublishedAt" }); + + migrationBuilder.CreateIndex( + name: "IX_BlogPostTags_BlogPostId", + schema: "CMS", + table: "BlogPostTags", + column: "BlogPostId"); + + migrationBuilder.CreateIndex( + name: "IX_BlogPostTags_TagId", + schema: "CMS", + table: "BlogPostTags", + column: "TagId"); + + migrationBuilder.CreateIndex( + name: "IX_SitePages_PageKey", + schema: "CMS", + table: "SitePages", + column: "PageKey", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_SitePageSections_PageId_SectionKey", + schema: "CMS", + table: "SitePageSections", + columns: new[] { "SitePageId", "SectionKey" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BlogPostCategories", + schema: "CMS"); + + migrationBuilder.DropTable( + name: "BlogPostImages", + schema: "CMS"); + + migrationBuilder.DropTable( + name: "BlogPostTags", + schema: "CMS"); + + migrationBuilder.DropTable( + name: "SitePageSections", + schema: "CMS"); + + migrationBuilder.DropTable( + name: "BlogCategories", + schema: "CMS"); + + migrationBuilder.DropTable( + name: "BlogPosts", + schema: "CMS"); + + migrationBuilder.DropTable( + name: "SitePages", + schema: "CMS"); + } + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260213123943_RemoveImagePathMaxLength.Designer.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260213123943_RemoveImagePathMaxLength.Designer.cs new file mode 100644 index 0000000..8361931 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260213123943_RemoveImagePathMaxLength.Designer.cs @@ -0,0 +1,4410 @@ +// +using System; +using CMSMicroservice.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CMSMicroservice.Infrastructure.Persistence.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20260213123943_RemoveImagePathMaxLength")] + partial class RemoveImagePathMaxLength + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("CMS") + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IconName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_BlogCategories_IsActive"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("IX_BlogCategories_Slug"); + + b.ToTable("BlogCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPost", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AuthorUserId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FeaturedImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("FeaturedImageThumbnailPath") + .HasColumnType("nvarchar(max)"); + + b.Property("HtmlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsFeatured") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PublishedAt") + .HasColumnType("datetime2"); + + b.Property("ScheduledPublishAt") + .HasColumnType("datetime2"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Summary") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ViewCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("AuthorUserId") + .HasDatabaseName("IX_BlogPosts_AuthorUserId"); + + b.HasIndex("IsFeatured") + .HasDatabaseName("IX_BlogPosts_IsFeatured"); + + b.HasIndex("PublishedAt") + .HasDatabaseName("IX_BlogPosts_PublishedAt"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("IX_BlogPosts_Slug"); + + b.HasIndex("Status") + .HasDatabaseName("IX_BlogPosts_Status"); + + b.HasIndex("Status", "PublishedAt") + .HasDatabaseName("IX_BlogPosts_Status_PublishedAt"); + + b.ToTable("BlogPosts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BlogCategoryId") + .HasColumnType("bigint"); + + b.Property("BlogPostId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("BlogCategoryId"); + + b.HasIndex("BlogPostId"); + + b.ToTable("BlogPostCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AltText") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("BlogPostId") + .HasColumnType("bigint"); + + b.Property("Caption") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ThumbnailPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("BlogPostId"); + + b.ToTable("BlogPostImages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BlogPostId") + .HasColumnType("bigint"); + + b.Property("TagId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("BlogPostId"); + + b.HasIndex("TagId"); + + b.ToTable("BlogPostTags", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ParentId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("ParentId"); + + b.ToTable("Categories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("IsActive", "SortOrder") + .HasDatabaseName("IX_ClubFeature_IsActive_SortOrder"); + + b.ToTable("ClubFeatures", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembership", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ActivatedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("GiftValue") + .HasColumnType("bigint"); + + b.Property("InitialContribution") + .HasColumnType("bigint"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("TotalEarned") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_ClubMembership_IsActive"); + + b.HasIndex("UserId") + .IsUnique() + .HasDatabaseName("IX_ClubMembership_UserId"); + + b.ToTable("ClubMemberships", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.UserClubFeature", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClubFeatureId") + .HasColumnType("bigint"); + + b.Property("ClubMembershipId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("GrantedAt") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ClubFeatureId"); + + b.HasIndex("ClubMembershipId") + .HasDatabaseName("IX_UserClubFeature_ClubMembershipId"); + + b.HasIndex("UserId", "ClubFeatureId") + .IsUnique() + .HasDatabaseName("IX_UserClubFeature_UserId_ClubFeatureId"); + + b.ToTable("UserClubFeatures", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BalancesEarned") + .HasColumnType("int"); + + b.Property("BankReferenceId") + .HasColumnType("nvarchar(max)"); + + b.Property("BankTrackingCode") + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IbanNumber") + .HasMaxLength(26) + .HasColumnType("nvarchar(26)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PaidAt") + .HasColumnType("datetime2"); + + b.Property("PaymentFailureReason") + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessedAt") + .HasColumnType("datetime2"); + + b.Property("ProcessedBy") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("RejectionReason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("ValuePerBalance") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.Property("WeeklyPoolId") + .HasColumnType("bigint"); + + b.Property("WithdrawalMethod") + .HasColumnType("int"); + + b.Property("WithdrawnAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("Status") + .HasDatabaseName("IX_UserCommissionPayout_Status"); + + b.HasIndex("WeekDefinitionId"); + + b.HasIndex("WeeklyPoolId") + .HasDatabaseName("IX_UserCommissionPayout_WeeklyPoolId"); + + b.HasIndex("UserId", "WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_UserCommissionPayout_UserId_WeekDefinitionId"); + + b.ToTable("UserCommissionPayouts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CalculatedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsCalculated") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("TotalBalances") + .HasColumnType("int"); + + b.Property("TotalPoolAmount") + .HasColumnType("bigint"); + + b.Property("ValuePerBalance") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("IsCalculated") + .HasDatabaseName("IX_WeeklyCommissionPool_IsCalculated"); + + b.HasIndex("WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_WeeklyCommissionPool_WeekDefinitionId"); + + b.ToTable("WeeklyCommissionPools", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WorkerExecutionLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Details") + .HasColumnType("nvarchar(max)"); + + b.Property("DurationMs") + .HasColumnType("bigint"); + + b.Property("ErrorCount") + .HasColumnType("int"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("ErrorStackTrace") + .HasColumnType("nvarchar(max)"); + + b.Property("ExecutionId") + .HasColumnType("uniqueidentifier"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessedCount") + .HasColumnType("int"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("StartedAt"); + + b.HasIndex("Status"); + + b.HasIndex("WeekDefinitionId"); + + b.ToTable("WorkerExecutionLogs", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Configuration.AppVersion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AppName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CurrentVersion") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MinRequiredVersion") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ReleaseNotes") + .HasColumnType("nvarchar(max)"); + + b.Property("RequiresFullCacheClear") + .HasColumnType("bit"); + + b.Property("UpdateMessage") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("AppVersions", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("HeroImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("HeroSubtitle") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HeroTitle") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MetaDescription") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("PageKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("PageKey") + .IsUnique() + .HasDatabaseName("IX_SitePages_PageKey"); + + b.ToTable("SitePages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExtraData") + .HasColumnType("nvarchar(max)"); + + b.Property("HtmlContent") + .HasColumnType("nvarchar(max)"); + + b.Property("IconName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("ImageThumbnailPath") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SectionKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SitePageId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Subtitle") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("SitePageId", "SectionKey") + .HasDatabaseName("IX_SitePageSections_PageId_SectionKey"); + + b.ToTable("SitePageSections", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Contract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("HtmlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Contracts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DayaLoanContract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ContractNumber") + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsProcessed") + .HasColumnType("bit"); + + b.Property("LastCheckDate") + .HasColumnType("datetime2"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NationalCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ProcessedDate") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserId"); + + b.ToTable("DayaLoanContracts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ParentCategoryId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("ParentCategoryId"); + + b.ToTable("DiscountCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeliveryDescription") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("DeliveryStatus") + .HasColumnType("int"); + + b.Property("DiscountBalanceUsed") + .HasColumnType("bigint"); + + b.Property("GatewayAmountPaid") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PaymentDate") + .HasColumnType("datetime2"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("TotalAmount") + .HasColumnType("bigint"); + + b.Property("TrackingCode") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserAddressId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("VatAmount") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserAddressId"); + + b.HasIndex("UserId"); + + b.ToTable("DiscountOrders", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrderDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountAmount") + .HasColumnType("bigint"); + + b.Property("DiscountOrderId") + .HasColumnType("bigint"); + + b.Property("DiscountPercentUsed") + .HasColumnType("int"); + + b.Property("FinalPrice") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UnitPrice") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DiscountOrderId"); + + b.HasIndex("ProductId"); + + b.ToTable("DiscountOrderDetails", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FullInformation") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MaxDiscountPercent") + .HasColumnType("int"); + + b.Property("Price") + .HasColumnType("bigint"); + + b.Property("Rate") + .HasColumnType("int"); + + b.Property("RemainingCount") + .HasColumnType("int"); + + b.Property("SaleCount") + .HasColumnType("int"); + + b.Property("ShortInfomation") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("ThumbnailPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ViewCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("DiscountProducts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ProductId", "CategoryId") + .IsUnique(); + + b.ToTable("DiscountProductCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AltText") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountProductId") + .HasColumnType("bigint"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("ThumbnailPath") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("DiscountProductId"); + + b.HasIndex("DiscountProductId", "SortOrder"); + + b.ToTable("DiscountProductImages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountShoppingCart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId", "ProductId") + .IsUnique(); + + b.ToTable("DiscountShoppingCarts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.FactorDetails", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsChangePrice") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UnitDiscount") + .HasColumnType("int"); + + b.Property("UnitDiscountPrice") + .HasColumnType("bigint"); + + b.Property("UnitPrice") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("ProductId"); + + b.ToTable("FactorDetails", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.City", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExternalId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Latitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Longitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Native") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasDefaultValue(""); + + b.Property("StateId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("StateId") + .HasDatabaseName("IX_Cities_StateId"); + + b.ToTable("Cities", "GMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.Country", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Capital") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Currency") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("CurrencyName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("CurrencySymbol") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Emoji") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("EmojiU") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("ExternalId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("Iso2") + .IsRequired() + .HasMaxLength(2) + .HasColumnType("nvarchar(2)"); + + b.Property("Iso3") + .IsRequired() + .HasMaxLength(3) + .HasColumnType("nvarchar(3)"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Latitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Longitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Native") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("NumericCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("PhoneCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Region") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Subregion") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Tld") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.HasKey("Id"); + + b.ToTable("Countries", "GMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.State", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CountryId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExternalId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Latitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Longitude") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Native") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasDefaultValue(""); + + b.Property("StateCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("CountryId") + .HasDatabaseName("IX_States_CountryId"); + + b.ToTable("States", "GMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("ClubMembershipId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewInitialContribution") + .HasColumnType("bigint"); + + b.Property("NewIsActive") + .HasColumnType("bit"); + + b.Property("OldInitialContribution") + .HasColumnType("bigint"); + + b.Property("OldIsActive") + .HasColumnType("bit"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_ClubMembershipHistory_Action"); + + b.HasIndex("ClubMembershipId") + .HasDatabaseName("IX_ClubMembershipHistory_ClubMembershipId"); + + b.HasIndex("UserId", "Created") + .HasDatabaseName("IX_ClubMembershipHistory_UserId_Created"); + + b.ToTable("ClubMembershipHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.CommissionPayoutHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("AmountAfter") + .HasColumnType("bigint"); + + b.Property("AmountBefore") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewStatus") + .HasColumnType("int"); + + b.Property("OldStatus") + .HasColumnType("int"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserCommissionPayoutId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_CommissionPayoutHistory_Action"); + + b.HasIndex("UserCommissionPayoutId") + .HasDatabaseName("IX_CommissionPayoutHistory_PayoutId"); + + b.HasIndex("WeekDefinitionId") + .HasDatabaseName("IX_CommissionPayoutHistory_WeekDefinitionId"); + + b.HasIndex("UserId", "Created") + .HasDatabaseName("IX_CommissionPayoutHistory_UserId_Created"); + + b.ToTable("CommissionPayoutHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.NetworkMembershipHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Action") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NewLegPosition") + .HasColumnType("int"); + + b.Property("NewParentId") + .HasColumnType("bigint"); + + b.Property("OldLegPosition") + .HasColumnType("int"); + + b.Property("OldParentId") + .HasColumnType("bigint"); + + b.Property("PerformedBy") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("Reason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("Action") + .HasDatabaseName("IX_NetworkMembershipHistory_Action"); + + b.HasIndex("UserId", "Created") + .HasDatabaseName("IX_NetworkMembershipHistory_UserId_Created"); + + b.ToTable("NetworkMembershipHistories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.InventoryItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountProductId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LastRestockedAt") + .HasColumnType("datetime2"); + + b.Property("LastSoldAt") + .HasColumnType("datetime2"); + + b.Property("LowStockThreshold") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(10); + + b.Property("MaxStockLevel") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(1000); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("ProductType") + .HasColumnType("int"); + + b.Property("Quantity") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ReorderPoint") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(5); + + b.Property("ReservedQuantity") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("WarehouseId") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasDefaultValue(1L); + + b.HasKey("Id"); + + b.HasIndex("DiscountProductId") + .HasDatabaseName("IX_InventoryItems_DiscountProductId"); + + b.HasIndex("ProductId") + .HasDatabaseName("IX_InventoryItems_ProductId"); + + b.HasIndex("WarehouseId") + .HasDatabaseName("IX_InventoryItems_WarehouseId"); + + b.HasIndex("ProductType", "Quantity") + .HasDatabaseName("IX_InventoryItems_ProductType_Quantity"); + + b.ToTable("InventoryItems", "CMS", t => + { + t.HasCheckConstraint("CK_InventoryItem_ProductReference", "(ProductId IS NOT NULL AND DiscountProductId IS NULL) OR (ProductId IS NULL AND DiscountProductId IS NOT NULL)"); + + t.HasCheckConstraint("CK_InventoryItem_ProductType_Match", "(ProductType = 1 AND ProductId IS NOT NULL) OR (ProductType = 2 AND DiscountProductId IS NOT NULL)"); + + t.HasCheckConstraint("CK_InventoryItem_Quantity_NonNegative", "Quantity >= 0"); + + t.HasCheckConstraint("CK_InventoryItem_ReservedQuantity_LessOrEqualQuantity", "ReservedQuantity <= Quantity"); + + t.HasCheckConstraint("CK_InventoryItem_ReservedQuantity_NonNegative", "ReservedQuantity >= 0"); + }); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Network.NetworkWeeklyBalance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CalculatedAt") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FlushedPerSide") + .HasColumnType("int"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsExpired") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LeftLegBalances") + .HasColumnType("int"); + + b.Property("LeftLegCarryover") + .HasColumnType("int"); + + b.Property("LeftLegNewMembers") + .HasColumnType("int"); + + b.Property("LeftLegRemainder") + .HasColumnType("int"); + + b.Property("LeftLegTotal") + .HasColumnType("int"); + + b.Property("RightLegBalances") + .HasColumnType("int"); + + b.Property("RightLegCarryover") + .HasColumnType("int"); + + b.Property("RightLegNewMembers") + .HasColumnType("int"); + + b.Property("RightLegRemainder") + .HasColumnType("int"); + + b.Property("RightLegTotal") + .HasColumnType("int"); + + b.Property("SubordinateBalances") + .HasColumnType("int"); + + b.Property("TotalBalances") + .HasColumnType("int"); + + b.Property("TotalFlushed") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.Property("WeekDefinitionId") + .HasColumnType("bigint"); + + b.Property("WeeklyPoolContribution") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("IsExpired") + .HasDatabaseName("IX_NetworkWeeklyBalance_IsExpired"); + + b.HasIndex("WeekDefinitionId") + .HasDatabaseName("IX_NetworkWeeklyBalance_WeekDefinitionId"); + + b.HasIndex("UserId", "WeekDefinitionId") + .IsUnique() + .HasDatabaseName("IX_NetworkWeeklyBalance_UserId_WeekDefinitionId"); + + b.ToTable("NetworkWeeklyBalances", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BaseAmount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsPaid") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("PaidAt") + .HasColumnType("datetime2"); + + b.Property("TotalAmount") + .HasColumnType("bigint"); + + b.Property("VATAmount") + .HasColumnType("bigint"); + + b.Property("VATRate") + .HasColumnType("decimal(5,4)"); + + b.HasKey("Id"); + + b.HasIndex("Created") + .HasDatabaseName("IX_OrderVATs_Created"); + + b.HasIndex("IsPaid") + .HasDatabaseName("IX_OrderVATs_IsPaid"); + + b.HasIndex("OrderId") + .IsUnique() + .HasDatabaseName("IX_OrderVATs_OrderId"); + + b.ToTable("OrderVATs", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.OtpToken", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Attempts") + .HasColumnType("int"); + + b.Property("CodeHash") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsUsed") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Mobile") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Purpose") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("OtpTokens", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Package", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("bigint"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Packages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Payment.ManualPayment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("ApprovedAt") + .HasColumnType("datetime2"); + + b.Property("ApprovedBy") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("ImageDocumentId") + .HasColumnType("bigint"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ReferenceNumber") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("RejectionReason") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("RequestedBy") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ApprovedBy"); + + b.HasIndex("Created"); + + b.HasIndex("RequestedBy"); + + b.HasIndex("Status"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserId"); + + b.HasIndex("UserId", "Status"); + + b.ToTable("ManualPayments", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Product", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ClubDiscountPercent") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Discount") + .HasColumnType("int"); + + b.Property("FullInformation") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsClubExclusive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Price") + .HasColumnType("bigint"); + + b.Property("Rate") + .HasColumnType("int"); + + b.Property("RemainingCount") + .HasColumnType("int"); + + b.Property("SaleCount") + .HasColumnType("int"); + + b.Property("ShortInfomation") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ThumbnailPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ViewCount") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("IsClubExclusive") + .HasDatabaseName("IX_Products_IsClubExclusive"); + + b.ToTable("Products", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("CategoryId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("CategoryId"); + + b.HasIndex("ProductId"); + + b.ToTable("ProductCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductGallery", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("ProductImageId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("ProductImageId"); + + b.ToTable("ProductGalleries", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ImageThumbnailPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("ProductImages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("TagId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("TagId"); + + b.ToTable("ProductTags", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.PublicMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ArchivedAt") + .HasColumnType("datetime2"); + + b.Property("Content") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("nvarchar(2000)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedByUserId") + .HasColumnType("bigint"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("ExpiresAt") + .HasColumnType("datetime2"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsArchived") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LinkText") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("LinkUrl") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("PublishedAt") + .HasColumnType("datetime2"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("StartsAt") + .HasColumnType("datetime2"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("ViewCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("CreatedByUserId") + .HasDatabaseName("IX_PublicMessages_CreatedByUserId"); + + b.HasIndex("ExpiresAt") + .HasDatabaseName("IX_PublicMessages_ExpiresAt"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_PublicMessages_IsActive"); + + b.HasIndex("Priority") + .HasDatabaseName("IX_PublicMessages_Priority"); + + b.HasIndex("StartsAt") + .HasDatabaseName("IX_PublicMessages_StartsAt"); + + b.HasIndex("Type") + .HasDatabaseName("IX_PublicMessages_Type"); + + b.HasIndex("IsActive", "ExpiresAt") + .HasDatabaseName("IX_PublicMessages_IsActive_ExpiresAt"); + + b.ToTable("PublicMessages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Role", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Roles", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.StockMovement", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountOrderId") + .HasColumnType("bigint"); + + b.Property("InventoryItemId") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MovementType") + .HasColumnType("int"); + + b.Property("Note") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("PerformedByUserId") + .HasColumnType("bigint"); + + b.Property("Quantity") + .HasColumnType("int"); + + b.Property("QuantityAfter") + .HasColumnType("int"); + + b.Property("QuantityBefore") + .HasColumnType("int"); + + b.Property("ReferenceNumber") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("Created") + .HasDatabaseName("IX_StockMovements_Created"); + + b.HasIndex("DiscountOrderId") + .HasDatabaseName("IX_StockMovements_DiscountOrderId") + .HasFilter("[DiscountOrderId] IS NOT NULL"); + + b.HasIndex("InventoryItemId") + .HasDatabaseName("IX_StockMovements_InventoryItemId"); + + b.HasIndex("MovementType") + .HasDatabaseName("IX_StockMovements_MovementType"); + + b.HasIndex("OrderId") + .HasDatabaseName("IX_StockMovements_OrderId") + .HasFilter("[OrderId] IS NOT NULL"); + + b.HasIndex("ReferenceNumber") + .HasDatabaseName("IX_StockMovements_ReferenceNumber") + .HasFilter("[ReferenceNumber] IS NOT NULL"); + + b.HasIndex("InventoryItemId", "MovementType", "Created") + .HasDatabaseName("IX_StockMovements_Item_Type_Date"); + + b.ToTable("StockMovements", "CMS", t => + { + t.HasCheckConstraint("CK_StockMovement_QuantityAfter_Calculation", "QuantityAfter = QuantityBefore + Quantity"); + + t.HasCheckConstraint("CK_StockMovement_QuantityAfter_NonNegative", "QuantityAfter >= 0"); + + t.HasCheckConstraint("CK_StockMovement_QuantityBefore_NonNegative", "QuantityBefore >= 0"); + }); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Tags", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Transaction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PaymentDate") + .HasColumnType("datetime2"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("RefId") + .HasColumnType("nvarchar(max)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Transactions", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AvatarPath") + .HasColumnType("nvarchar(max)"); + + b.Property("BirthDate") + .HasColumnType("datetime2"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DayaCreditReceivedAt") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("EmailNotifications") + .HasColumnType("bit"); + + b.Property("FirstName") + .HasColumnType("nvarchar(max)"); + + b.Property("HasReceivedDayaCredit") + .HasColumnType("bit"); + + b.Property("HashPassword") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsMobileVerified") + .HasColumnType("bit"); + + b.Property("IsRulesAccepted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .HasColumnType("nvarchar(max)"); + + b.Property("LegPosition") + .HasColumnType("int"); + + b.Property("Mobile") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("MobileVerifiedAt") + .HasColumnType("datetime2"); + + b.Property("NationalCode") + .HasColumnType("nvarchar(max)"); + + b.Property("NetworkParentId") + .HasColumnType("bigint"); + + b.Property("PackagePurchaseMethod") + .HasColumnType("int"); + + b.Property("PushNotifications") + .HasColumnType("bit"); + + b.Property("ReferralCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RulesAcceptedAt") + .HasColumnType("datetime2"); + + b.Property("SmsNotifications") + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("LegPosition") + .HasDatabaseName("IX_User_LegPosition"); + + b.HasIndex("NetworkParentId") + .HasDatabaseName("IX_User_NetworkParentId"); + + b.ToTable("Users", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserAddress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Address") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CityId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDefault") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserAddresses", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserCart", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Count") + .HasColumnType("int"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ProductId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ProductId"); + + b.HasIndex("UserId"); + + b.ToTable("UserCarts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserContract", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ContractId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SignGuid") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("SignedPdfFile") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("ContractId"); + + b.HasIndex("UserId"); + + b.ToTable("UserContracts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DeliveryDescription") + .HasColumnType("nvarchar(max)"); + + b.Property("DeliveryStatus") + .HasColumnType("int"); + + b.Property("HasVAT") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("PaymentDate") + .HasColumnType("datetime2"); + + b.Property("PaymentMethod") + .HasColumnType("int"); + + b.Property("PaymentStatus") + .HasColumnType("int"); + + b.Property("TrackingCode") + .HasColumnType("nvarchar(max)"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserAddressId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("PackageId"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserAddressId"); + + b.HasIndex("UserId"); + + b.ToTable("UserOrders", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserPackagePurchase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Amount") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("OrderId") + .HasColumnType("bigint"); + + b.Property("PackageId") + .HasColumnType("bigint"); + + b.Property("PurchaseMethod") + .HasColumnType("int"); + + b.Property("PurchasedAt") + .HasColumnType("datetime2"); + + b.Property("TransactionId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("OrderId"); + + b.HasIndex("PackageId") + .HasDatabaseName("IX_UserPackagePurchase_PackageId"); + + b.HasIndex("PurchasedAt") + .HasDatabaseName("IX_UserPackagePurchase_PurchasedAt"); + + b.HasIndex("TransactionId"); + + b.HasIndex("UserId") + .HasDatabaseName("IX_UserPackagePurchase_UserId"); + + b.HasIndex("UserId", "PurchasedAt") + .HasDatabaseName("IX_UserPackagePurchase_UserId_PurchasedAt"); + + b.ToTable("UserPackagePurchases", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("RoleId") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.HasIndex("UserId"); + + b.ToTable("UserRoles", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Balance") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DiscountBalance") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("NetworkBalance") + .HasColumnType("bigint"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("UserWallets", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWalletChangeLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("ChangeDiscountValue") + .HasColumnType("bigint"); + + b.Property("ChangeNerworkValue") + .HasColumnType("bigint"); + + b.Property("ChangeValue") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("CurrentBalance") + .HasColumnType("bigint"); + + b.Property("CurrentDiscountBalance") + .HasColumnType("bigint"); + + b.Property("CurrentNetworkBalance") + .HasColumnType("bigint"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsIncrease") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("RefrenceId") + .HasColumnType("bigint"); + + b.Property("WalletId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("WalletId"); + + b.ToTable("UserWalletChangeLogs", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Warehouse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Address") + .HasMaxLength(1000) + .HasColumnType("nvarchar(1000)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDefault") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique() + .HasDatabaseName("IX_Warehouses_Code_Unique"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_Warehouses_IsActive"); + + b.HasIndex("IsDefault") + .HasDatabaseName("IX_Warehouses_IsDefault") + .HasFilter("[IsDefault] = 1"); + + b.ToTable("Warehouses", "CMS"); + + b.HasData( + new + { + Id = 1L, + Address = "تهران - انبار مرکزی فروشگاه", + Code = "WH-001", + Created = new DateTime(2026, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc), + CreatedBy = "System", + IsActive = true, + IsDefault = true, + IsDeleted = false, + Name = "انبار اصلی" + }); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.WeekDefinition", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("GregorianWeekNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("GregorianYear") + .HasColumnType("int"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PersianWeekNumber") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)"); + + b.Property("PersianYear") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.Property("WeekOrder") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("GregorianWeekNumber") + .IsUnique() + .HasDatabaseName("IX_WeekDefinition_GregorianWeekNumber"); + + b.HasIndex("GregorianYear") + .HasDatabaseName("IX_WeekDefinition_GregorianYear"); + + b.HasIndex("PersianWeekNumber") + .HasDatabaseName("IX_WeekDefinition_PersianWeekNumber"); + + b.HasIndex("PersianYear") + .HasDatabaseName("IX_WeekDefinition_PersianYear"); + + b.HasIndex("StartDate") + .HasDatabaseName("IX_WeekDefinition_StartDate"); + + b.ToTable("WeekDefinitions", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogCategory", "BlogCategory") + .WithMany("BlogPostCategories") + .HasForeignKey("BlogCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost") + .WithMany("BlogPostCategories") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogCategory"); + + b.Navigation("BlogPost"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostImage", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost") + .WithMany("BlogPostImages") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogPost"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostTag", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost") + .WithMany("BlogPostTags") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogPost"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Category", "Parent") + .WithMany("Categories") + .HasForeignKey("ParentId"); + + b.Navigation("Parent"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembership", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithOne("ClubMembership") + .HasForeignKey("CMSMicroservice.Domain.Entities.Club.ClubMembership", "UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.UserClubFeature", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubFeature", "ClubFeature") + .WithMany("UserClubFeatures") + .HasForeignKey("ClubFeatureId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubMembership", "ClubMembership") + .WithMany("UserClubFeatures") + .HasForeignKey("ClubMembershipId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserClubFeatures") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ClubFeature"); + + b.Navigation("ClubMembership"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("CommissionPayouts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("UserCommissionPayouts") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", "WeeklyPool") + .WithMany("UserCommissionPayouts") + .HasForeignKey("WeeklyPoolId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("User"); + + b.Navigation("WeekDefinition"); + + b.Navigation("WeeklyPool"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("WeeklyCommissionPools") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WorkerExecutionLog", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("WorkerExecutionLogs") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSection", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Content.SitePage", "SitePage") + .WithMany("Sections") + .HasForeignKey("SitePageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SitePage"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DayaLoanContract", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId"); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("DayaLoanContracts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Transaction"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", "ParentCategory") + .WithMany("ChildCategories") + .HasForeignKey("ParentCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("ParentCategory"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.UserAddress", "UserAddress") + .WithMany() + .HasForeignKey("UserAddressId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("DiscountOrders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Transaction"); + + b.Navigation("User"); + + b.Navigation("UserAddress"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrderDetail", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", "DiscountOrder") + .WithMany("OrderDetails") + .HasForeignKey("DiscountOrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "Product") + .WithMany("OrderDetails") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DiscountOrder"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", "Category") + .WithMany("ProductCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "Product") + .WithMany("ProductCategories") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProductImage", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "DiscountProduct") + .WithMany("Images") + .HasForeignKey("DiscountProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DiscountProduct"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountShoppingCart", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "Product") + .WithMany("ShoppingCarts") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("DiscountShoppingCarts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.FactorDetails", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithMany("FactorDetails") + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("FactorDetails") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.City", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Geography.State", "State") + .WithMany("Cities") + .HasForeignKey("StateId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("State"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.State", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Geography.Country", "Country") + .WithMany("States") + .HasForeignKey("CountryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Country"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.ClubMembershipHistory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Club.ClubMembership", "ClubMembership") + .WithMany("ClubMembershipHistories") + .HasForeignKey("ClubMembershipId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ClubMembership"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.History.CommissionPayoutHistory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", "UserCommissionPayout") + .WithMany("CommissionPayoutHistories") + .HasForeignKey("UserCommissionPayoutId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("CommissionPayoutHistories") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("UserCommissionPayout"); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.InventoryItem", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", "DiscountProduct") + .WithMany() + .HasForeignKey("DiscountProductId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany() + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("CMSMicroservice.Domain.Entities.Warehouse", "Warehouse") + .WithMany("InventoryItems") + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DiscountProduct"); + + b.Navigation("Product"); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Network.NetworkWeeklyBalance", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("NetworkWeeklyBalances") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.WeekDefinition", "WeekDefinition") + .WithMany("NetworkWeeklyBalances") + .HasForeignKey("WeekDefinitionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("User"); + + b.Navigation("WeekDefinition"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithOne("OrderVAT") + .HasForeignKey("CMSMicroservice.Domain.Entities.Order.OrderVAT", "OrderId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Payment.ManualPayment", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Transaction"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Category", "Category") + .WithMany("ProductCategories") + .HasForeignKey("CategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("ProductCategories") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Category"); + + b.Navigation("Product"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductGallery", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("ProductGalleries") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.ProductImage", "ProductImage") + .WithMany("ProductGalleries") + .HasForeignKey("ProductImageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("ProductImage"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductTag", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("ProductTags") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Tag", "Tag") + .WithMany("ProductTags") + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("Tag"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.StockMovement", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.InventoryItem", "InventoryItem") + .WithMany("StockMovements") + .HasForeignKey("InventoryItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("InventoryItem"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.User", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "NetworkParent") + .WithMany("NetworkChildren") + .HasForeignKey("NetworkParentId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("NetworkParent"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserAddress", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserAddresses") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserCart", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Product", "Product") + .WithMany("UserCarts") + .HasForeignKey("ProductId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserCarts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Product"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserContract", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Contract", "Contract") + .WithMany("UserContracts") + .HasForeignKey("ContractId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserContracts") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Contract"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany("UserOrders") + .HasForeignKey("PackageId"); + + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany("UserOrders") + .HasForeignKey("TransactionId"); + + b.HasOne("CMSMicroservice.Domain.Entities.UserAddress", "UserAddress") + .WithMany("UserOrders") + .HasForeignKey("UserAddressId") + .OnDelete(DeleteBehavior.NoAction) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserOrders") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Package"); + + b.Navigation("Transaction"); + + b.Navigation("User"); + + b.Navigation("UserAddress"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserPackagePurchase", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") + .WithMany() + .HasForeignKey("OrderId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") + .WithMany() + .HasForeignKey("PackageId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") + .WithMany() + .HasForeignKey("TransactionId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Order"); + + b.Navigation("Package"); + + b.Navigation("Transaction"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserRole", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Role", "Role") + .WithMany("UserRoles") + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserRoles") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.User", "User") + .WithMany("UserWallets") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWalletChangeLog", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.UserWallet", "Wallet") + .WithMany("UserWalletChangeLogs") + .HasForeignKey("WalletId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Wallet"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogCategory", b => + { + b.Navigation("BlogPostCategories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPost", b => + { + b.Navigation("BlogPostCategories"); + + b.Navigation("BlogPostImages"); + + b.Navigation("BlogPostTags"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b => + { + b.Navigation("Categories"); + + b.Navigation("ProductCategories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubFeature", b => + { + b.Navigation("UserClubFeatures"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Club.ClubMembership", b => + { + b.Navigation("ClubMembershipHistories"); + + b.Navigation("UserClubFeatures"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.UserCommissionPayout", b => + { + b.Navigation("CommissionPayoutHistories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Commission.WeeklyCommissionPool", b => + { + b.Navigation("UserCommissionPayouts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePage", b => + { + b.Navigation("Sections"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Contract", b => + { + b.Navigation("UserContracts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountCategory", b => + { + b.Navigation("ChildCategories"); + + b.Navigation("ProductCategories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountOrder", b => + { + b.Navigation("OrderDetails"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DiscountShop.DiscountProduct", b => + { + b.Navigation("Images"); + + b.Navigation("OrderDetails"); + + b.Navigation("ProductCategories"); + + b.Navigation("ShoppingCarts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.Country", b => + { + b.Navigation("States"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Geography.State", b => + { + b.Navigation("Cities"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.InventoryItem", b => + { + b.Navigation("StockMovements"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Package", b => + { + b.Navigation("UserOrders"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Product", b => + { + b.Navigation("FactorDetails"); + + b.Navigation("ProductCategories"); + + b.Navigation("ProductGalleries"); + + b.Navigation("ProductTags"); + + b.Navigation("UserCarts"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.ProductImage", b => + { + b.Navigation("ProductGalleries"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Role", b => + { + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Tag", b => + { + b.Navigation("ProductTags"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Transaction", b => + { + b.Navigation("UserOrders"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.User", b => + { + b.Navigation("ClubMembership"); + + b.Navigation("CommissionPayouts"); + + b.Navigation("DayaLoanContracts"); + + b.Navigation("DiscountOrders"); + + b.Navigation("DiscountShoppingCarts"); + + b.Navigation("NetworkChildren"); + + b.Navigation("NetworkWeeklyBalances"); + + b.Navigation("UserAddresses"); + + b.Navigation("UserCarts"); + + b.Navigation("UserClubFeatures"); + + b.Navigation("UserContracts"); + + b.Navigation("UserOrders"); + + b.Navigation("UserRoles"); + + b.Navigation("UserWallets"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserAddress", b => + { + b.Navigation("UserOrders"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => + { + b.Navigation("FactorDetails"); + + b.Navigation("OrderVAT"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b => + { + b.Navigation("UserWalletChangeLogs"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Warehouse", b => + { + b.Navigation("InventoryItems"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.WeekDefinition", b => + { + b.Navigation("CommissionPayoutHistories"); + + b.Navigation("NetworkWeeklyBalances"); + + b.Navigation("UserCommissionPayouts"); + + b.Navigation("WeeklyCommissionPools"); + + b.Navigation("WorkerExecutionLogs"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260213123943_RemoveImagePathMaxLength.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260213123943_RemoveImagePathMaxLength.cs new file mode 100644 index 0000000..c8f72fd --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/20260213123943_RemoveImagePathMaxLength.cs @@ -0,0 +1,309 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CMSMicroservice.Infrastructure.Persistence.Migrations +{ + /// + public partial class RemoveImagePathMaxLength : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_UserOrders_OrderVATs_OrderVATId", + schema: "CMS", + table: "UserOrders"); + + migrationBuilder.DropIndex( + name: "IX_UserOrders_OrderVATId", + schema: "CMS", + table: "UserOrders"); + + migrationBuilder.DropColumn( + name: "OrderVATId", + schema: "CMS", + table: "UserOrders"); + + migrationBuilder.AlterColumn( + name: "ImageThumbnailPath", + schema: "CMS", + table: "SitePageSections", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "SitePageSections", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "HeroImagePath", + schema: "CMS", + table: "SitePages", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ThumbnailPath", + schema: "CMS", + table: "DiscountProducts", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "DiscountProducts", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500); + + migrationBuilder.AlterColumn( + name: "ThumbnailPath", + schema: "CMS", + table: "DiscountProductImages", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "DiscountProductImages", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "DiscountCategories", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "FeaturedImageThumbnailPath", + schema: "CMS", + table: "BlogPosts", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "FeaturedImagePath", + schema: "CMS", + table: "BlogPosts", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500, + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ThumbnailPath", + schema: "CMS", + table: "BlogPostImages", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "BlogPostImages", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(500)", + oldMaxLength: 500); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "OrderVATId", + schema: "CMS", + table: "UserOrders", + type: "bigint", + nullable: true); + + migrationBuilder.AlterColumn( + name: "ImageThumbnailPath", + schema: "CMS", + table: "SitePageSections", + type: "nvarchar(500)", + maxLength: 500, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "SitePageSections", + type: "nvarchar(500)", + maxLength: 500, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "HeroImagePath", + schema: "CMS", + table: "SitePages", + type: "nvarchar(500)", + maxLength: 500, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ThumbnailPath", + schema: "CMS", + table: "DiscountProducts", + type: "nvarchar(500)", + maxLength: 500, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "DiscountProducts", + type: "nvarchar(500)", + maxLength: 500, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + + migrationBuilder.AlterColumn( + name: "ThumbnailPath", + schema: "CMS", + table: "DiscountProductImages", + type: "nvarchar(500)", + maxLength: 500, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "DiscountProductImages", + type: "nvarchar(500)", + maxLength: 500, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "DiscountCategories", + type: "nvarchar(500)", + maxLength: 500, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "FeaturedImageThumbnailPath", + schema: "CMS", + table: "BlogPosts", + type: "nvarchar(500)", + maxLength: 500, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "FeaturedImagePath", + schema: "CMS", + table: "BlogPosts", + type: "nvarchar(500)", + maxLength: 500, + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "ThumbnailPath", + schema: "CMS", + table: "BlogPostImages", + type: "nvarchar(500)", + maxLength: 500, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + + migrationBuilder.AlterColumn( + name: "ImagePath", + schema: "CMS", + table: "BlogPostImages", + type: "nvarchar(500)", + maxLength: 500, + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + + migrationBuilder.CreateIndex( + name: "IX_UserOrders_OrderVATId", + schema: "CMS", + table: "UserOrders", + column: "OrderVATId"); + + migrationBuilder.AddForeignKey( + name: "FK_UserOrders_OrderVATs_OrderVATId", + schema: "CMS", + table: "UserOrders", + column: "OrderVATId", + principalSchema: "CMS", + principalTable: "OrderVATs", + principalColumn: "Id"); + } + } +} diff --git a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs index bbfc468..6e52449 100644 --- a/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/CMSMicroservice.Infrastructure/Persistence/Migrations/ApplicationDbContextModelSnapshot.cs @@ -23,6 +23,267 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("IconName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Title") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_BlogCategories_IsActive"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("IX_BlogCategories_Slug"); + + b.ToTable("BlogCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPost", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AuthorUserId") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("FeaturedImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("FeaturedImageThumbnailPath") + .HasColumnType("nvarchar(max)"); + + b.Property("HtmlContent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("IsFeatured") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(false); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("PublishedAt") + .HasColumnType("datetime2"); + + b.Property("ScheduledPublishAt") + .HasColumnType("datetime2"); + + b.Property("Slug") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Summary") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("ViewCount") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.HasKey("Id"); + + b.HasIndex("AuthorUserId") + .HasDatabaseName("IX_BlogPosts_AuthorUserId"); + + b.HasIndex("IsFeatured") + .HasDatabaseName("IX_BlogPosts_IsFeatured"); + + b.HasIndex("PublishedAt") + .HasDatabaseName("IX_BlogPosts_PublishedAt"); + + b.HasIndex("Slug") + .IsUnique() + .HasDatabaseName("IX_BlogPosts_Slug"); + + b.HasIndex("Status") + .HasDatabaseName("IX_BlogPosts_Status"); + + b.HasIndex("Status", "PublishedAt") + .HasDatabaseName("IX_BlogPosts_Status_PublishedAt"); + + b.ToTable("BlogPosts", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BlogCategoryId") + .HasColumnType("bigint"); + + b.Property("BlogPostId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("BlogCategoryId"); + + b.HasIndex("BlogPostId"); + + b.ToTable("BlogPostCategories", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("AltText") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("BlogPostId") + .HasColumnType("bigint"); + + b.Property("Caption") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ImagePath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("ThumbnailPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("BlogPostId"); + + b.ToTable("BlogPostImages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("BlogPostId") + .HasColumnType("bigint"); + + b.Property("TagId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("BlogPostId"); + + b.HasIndex("TagId"); + + b.ToTable("BlogPostTags", "CMS"); + }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b => { b.Property("Id") @@ -503,6 +764,142 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.ToTable("AppVersions", "CMS"); }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("HeroImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("HeroSubtitle") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)"); + + b.Property("HeroTitle") + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("MetaDescription") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("PageKey") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("PageKey") + .IsUnique() + .HasDatabaseName("IX_SitePages_PageKey"); + + b.ToTable("SitePages", "CMS"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("Created") + .HasColumnType("datetime2"); + + b.Property("CreatedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("ExtraData") + .HasColumnType("nvarchar(max)"); + + b.Property("HtmlContent") + .HasColumnType("nvarchar(max)"); + + b.Property("IconName") + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("ImagePath") + .HasColumnType("nvarchar(max)"); + + b.Property("ImageThumbnailPath") + .HasColumnType("nvarchar(max)"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("bit") + .HasDefaultValue(true); + + b.Property("IsDeleted") + .HasColumnType("bit"); + + b.Property("LastModified") + .HasColumnType("datetime2"); + + b.Property("LastModifiedBy") + .HasColumnType("nvarchar(max)"); + + b.Property("SectionKey") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)"); + + b.Property("SitePageId") + .HasColumnType("bigint"); + + b.Property("SortOrder") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasDefaultValue(0); + + b.Property("Subtitle") + .HasMaxLength(300) + .HasColumnType("nvarchar(300)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("nvarchar(200)"); + + b.HasKey("Id"); + + b.HasIndex("SitePageId", "SectionKey") + .HasDatabaseName("IX_SitePageSections_PageId_SectionKey"); + + b.ToTable("SitePageSections", "CMS"); + }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Contract", b => { b.Property("Id") @@ -622,8 +1019,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations .HasColumnType("nvarchar(1000)"); b.Property("ImagePath") - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); + .HasColumnType("nvarchar(max)"); b.Property("IsActive") .ValueGeneratedOnAdd() @@ -808,8 +1204,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Property("ImagePath") .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); + .HasColumnType("nvarchar(max)"); b.Property("IsActive") .ValueGeneratedOnAdd() @@ -847,8 +1242,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Property("ThumbnailPath") .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); + .HasColumnType("nvarchar(max)"); b.Property("Title") .IsRequired() @@ -925,8 +1319,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Property("ImagePath") .IsRequired() - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); + .HasColumnType("nvarchar(max)"); b.Property("IsActive") .HasColumnType("bit"); @@ -944,8 +1337,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations .HasColumnType("int"); b.Property("ThumbnailPath") - .HasMaxLength(500) - .HasColumnType("nvarchar(500)"); + .HasColumnType("nvarchar(max)"); b.Property("Title") .HasMaxLength(200) @@ -2767,9 +3159,6 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Property("LastModifiedBy") .HasColumnType("nvarchar(max)"); - b.Property("OrderVATId") - .HasColumnType("bigint"); - b.Property("PackageId") .HasColumnType("bigint"); @@ -2796,8 +3185,6 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.HasKey("Id"); - b.HasIndex("OrderVATId"); - b.HasIndex("PackageId"); b.HasIndex("TransactionId"); @@ -3167,6 +3554,55 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.ToTable("WeekDefinitions", "CMS"); }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostCategory", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogCategory", "BlogCategory") + .WithMany("BlogPostCategories") + .HasForeignKey("BlogCategoryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost") + .WithMany("BlogPostCategories") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogCategory"); + + b.Navigation("BlogPost"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostImage", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost") + .WithMany("BlogPostImages") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogPost"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPostTag", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Blog.BlogPost", "BlogPost") + .WithMany("BlogPostTags") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("CMSMicroservice.Domain.Entities.Tag", "Tag") + .WithMany() + .HasForeignKey("TagId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogPost"); + + b.Navigation("Tag"); + }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b => { b.HasOne("CMSMicroservice.Domain.Entities.Category", "Parent") @@ -3263,6 +3699,17 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Navigation("WeekDefinition"); }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePageSection", b => + { + b.HasOne("CMSMicroservice.Domain.Entities.Content.SitePage", "SitePage") + .WithMany("Sections") + .HasForeignKey("SitePageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SitePage"); + }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.DayaLoanContract", b => { b.HasOne("CMSMicroservice.Domain.Entities.Transaction", "Transaction") @@ -3502,7 +3949,7 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations modelBuilder.Entity("CMSMicroservice.Domain.Entities.Order.OrderVAT", b => { b.HasOne("CMSMicroservice.Domain.Entities.UserOrder", "Order") - .WithOne() + .WithOne("OrderVAT") .HasForeignKey("CMSMicroservice.Domain.Entities.Order.OrderVAT", "OrderId") .OnDelete(DeleteBehavior.Restrict) .IsRequired(); @@ -3657,10 +4104,6 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => { - b.HasOne("CMSMicroservice.Domain.Entities.Order.OrderVAT", "OrderVAT") - .WithMany() - .HasForeignKey("OrderVATId"); - b.HasOne("CMSMicroservice.Domain.Entities.Package", "Package") .WithMany("UserOrders") .HasForeignKey("PackageId"); @@ -3681,8 +4124,6 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); - b.Navigation("OrderVAT"); - b.Navigation("Package"); b.Navigation("Transaction"); @@ -3766,6 +4207,20 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Navigation("Wallet"); }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogCategory", b => + { + b.Navigation("BlogPostCategories"); + }); + + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Blog.BlogPost", b => + { + b.Navigation("BlogPostCategories"); + + b.Navigation("BlogPostImages"); + + b.Navigation("BlogPostTags"); + }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Category", b => { b.Navigation("Categories"); @@ -3795,6 +4250,11 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations b.Navigation("UserCommissionPayouts"); }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Content.SitePage", b => + { + b.Navigation("Sections"); + }); + modelBuilder.Entity("CMSMicroservice.Domain.Entities.Contract", b => { b.Navigation("UserContracts"); @@ -3915,6 +4375,8 @@ namespace CMSMicroservice.Infrastructure.Persistence.Migrations modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserOrder", b => { b.Navigation("FactorDetails"); + + b.Navigation("OrderVAT"); }); modelBuilder.Entity("CMSMicroservice.Domain.Entities.UserWallet", b => diff --git a/src/CMSMicroservice.Infrastructure/Services/FileManagementService.cs b/src/CMSMicroservice.Infrastructure/Services/FileManagementService.cs deleted file mode 100644 index 9465ff5..0000000 --- a/src/CMSMicroservice.Infrastructure/Services/FileManagementService.cs +++ /dev/null @@ -1,139 +0,0 @@ -using CMSMicroservice.Application.Common.Interfaces; -using CMSMicroservice.Protobuf.Protos.FMS; -using Google.Protobuf; -using Grpc.Net.Client; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using SixLabors.ImageSharp; -using SixLabors.ImageSharp.Formats.Jpeg; -using SixLabors.ImageSharp.Processing; -using System.IO; - -namespace CMSMicroservice.Infrastructure.Services; - -public class FileManagementService : IFileManagementService, IDisposable -{ - private readonly ILogger _logger; - private readonly FileInfoContract.FileInfoContractClient _client; - private readonly GrpcChannel _channel; - - private const int MainImageMaxWidth = 1200; - private const int MainImageMaxHeight = 1200; - private const int ThumbnailMaxWidth = 300; - private const int ThumbnailMaxHeight = 300; - private const int JpegQuality = 75; - - public FileManagementService(IConfiguration configuration, ILogger logger) - { - _logger = logger; - - var fmsAddress = configuration["FMS:Address"] ?? "https://dl.afrino.co"; - - _channel = GrpcChannel.ForAddress(fmsAddress, new GrpcChannelOptions - { - MaxReceiveMessageSize = 100 * 1024 * 1024, // 100 MB - MaxSendMessageSize = 100 * 1024 * 1024 - }); - - _client = new FileInfoContract.FileInfoContractClient(_channel); - } - - public async Task UploadFileAsync(string directory, byte[] fileBytes, string mime, string? fileName, - CancellationToken cancellationToken = default) - { - try - { - var request = new CreateNewFileInfoRequest - { - Directory = directory, - File = ByteString.CopyFrom(fileBytes), - Mime = mime, - IsBase64 = false - }; - - if (!string.IsNullOrWhiteSpace(fileName)) - request.FileName = fileName; - - var response = await _client.CreateNewFileInfoAsync(request, cancellationToken: cancellationToken); - - if (response != null && !string.IsNullOrWhiteSpace(response.File)) - { - _logger.LogInformation("File uploaded to FMS successfully. Id: {Id}, Path: {Path}", response.Id, response.File); - return response.File; - } - - _logger.LogWarning("FMS upload returned null or empty path for file: {FileName}", fileName); - return null; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error uploading file to FMS. Directory: {Directory}, FileName: {FileName}", directory, fileName); - return null; - } - } - - public async Task<(string? MainImagePath, string? ThumbnailPath)> UploadImageWithThumbnailAsync( - string directory, byte[] fileBytes, string mime, string? fileName, - CancellationToken cancellationToken = default) - { - string? mainImagePath = null; - string? thumbnailPath = null; - - try - { - // Optimize main image - var mainImageBytes = await OptimizeImageAsync(fileBytes, MainImageMaxWidth, MainImageMaxHeight); - mainImagePath = await UploadFileAsync(directory, mainImageBytes, "image/jpeg", fileName, cancellationToken); - - // Create and upload thumbnail - var thumbnailBytes = await OptimizeImageAsync(fileBytes, ThumbnailMaxWidth, ThumbnailMaxHeight); - var thumbFileName = fileName != null ? $"thumb_{fileName}" : null; - thumbnailPath = await UploadFileAsync($"{directory}/Thumbnails", thumbnailBytes, "image/jpeg", thumbFileName, cancellationToken); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error processing and uploading image with thumbnail. Directory: {Directory}", directory); - } - - return (mainImagePath, thumbnailPath); - } - - public async Task DeleteFileAsync(long fileId, CancellationToken cancellationToken = default) - { - try - { - var request = new DeleteFileInfoRequest { Id = fileId }; - var response = await _client.DeleteFileInfoAsync(request, cancellationToken: cancellationToken); - return response?.Success ?? false; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error deleting file from FMS. FileId: {FileId}", fileId); - return false; - } - } - - private static async Task OptimizeImageAsync(byte[] imageBytes, int maxWidth, int maxHeight) - { - using var image = Image.Load(imageBytes); - - // Only resize if larger than max dimensions - if (image.Width > maxWidth || image.Height > maxHeight) - { - image.Mutate(x => x.Resize(new ResizeOptions - { - Size = new Size(maxWidth, maxHeight), - Mode = ResizeMode.Max - })); - } - - using var ms = new MemoryStream(); - await image.SaveAsJpegAsync(ms, new JpegEncoder { Quality = JpegQuality }); - return ms.ToArray(); - } - - public void Dispose() - { - _channel?.Dispose(); - } -} diff --git a/src/CMSMicroservice.Infrastructure/Services/LocalFileManager.cs b/src/CMSMicroservice.Infrastructure/Services/LocalFileManager.cs new file mode 100644 index 0000000..0df4695 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Services/LocalFileManager.cs @@ -0,0 +1,260 @@ +using System.IO; +using System.Net.Http; +using CMSMicroservice.Application.Common.FileManager; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.Formats.Jpeg; +using SixLabors.ImageSharp.Processing; + +namespace CMSMicroservice.Infrastructure.Services; + +/// +/// فایل‌منیجر محلی — فایل‌ها روی دیسک ذخیره می‌شوند +/// مسیر نسبی در دیتابیس ذخیره می‌شود +/// موقع واکشی: فایل از دیسک خوانده و به base64 data-URI تبدیل می‌شود +/// +public sealed class LocalFileManager : IFileManager +{ + private readonly ILogger _logger; + private readonly IHttpClientFactory _httpClientFactory; + private readonly string _uploadRoot; + private readonly string _fmsBaseUrl; + + // ── تنظیمات بهینه‌سازی تصویر ── + private const int MainMaxWidth = 1200; + private const int MainMaxHeight = 1200; + private const int ThumbMaxWidth = 300; + private const int ThumbMaxHeight = 300; + private const int JpegQuality = 75; + + public LocalFileManager(IConfiguration configuration, IHttpClientFactory httpClientFactory, ILogger logger) + { + _logger = logger; + _httpClientFactory = httpClientFactory; + + // مسیر ذخیره فایل‌ها — پیش‌فرض: پوشه Uploads در کنار WebApi + _uploadRoot = configuration["FileStorage:UploadPath"] + ?? Path.Combine(AppContext.BaseDirectory, "Uploads"); + + _fmsBaseUrl = configuration["FMS:Address"]?.TrimEnd('/') ?? "https://dl.afrino.co"; + + Directory.CreateDirectory(_uploadRoot); + _logger.LogInformation("LocalFileManager initialized — UploadRoot: {Root}", _uploadRoot); + } + + // ──────────────────────────────────────────────────── + // آپلود فایل خام → ذخیره روی دیسک → برگرداندن مسیر نسبی + // ──────────────────────────────────────────────────── + public async Task UploadAsync( + string directory, byte[] fileBytes, string mime, + string? fileName = null, CancellationToken ct = default) + { + if (fileBytes is not { Length: > 0 }) + throw new FileUploadException("فایلی برای آپلود ارسال نشده است"); + + try + { + var ext = GetExtension(mime, fileName); + var uniqueName = $"{Guid.NewGuid():N}{ext}"; + var relativePath = Path.Combine(directory, uniqueName).Replace('\\', '/'); + + var fullPath = Path.Combine(_uploadRoot, relativePath); + Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!); + + await File.WriteAllBytesAsync(fullPath, fileBytes, ct); + + _logger.LogInformation( + "File saved — Path: {Path}, Size: {Size}KB", + relativePath, fileBytes.Length / 1024); + + return new UploadedFile(0, relativePath); + } + catch (FileUploadException) { throw; } + catch (Exception ex) + { + _logger.LogError(ex, "خطا در ذخیره فایل — Directory: {Dir}", directory); + throw new FileUploadException($"خطا در ذخیره فایل: {ex.Message}", ex); + } + } + + // ──────────────────────────────────────────────────── + // آپلود تصویر + بندانگشتی → ذخیره روی دیسک + // ──────────────────────────────────────────────────── + public async Task UploadImageAsync( + string directory, byte[] fileBytes, string mime, + string? fileName = null, CancellationToken ct = default) + { + if (fileBytes is not { Length: > 0 }) + throw new FileUploadException("تصویری برای آپلود ارسال نشده است"); + + var baseName = Guid.NewGuid().ToString("N"); + + // ① بهینه‌سازی و ذخیره تصویر اصلی + var mainBytes = await OptimizeAsync(fileBytes, MainMaxWidth, MainMaxHeight); + var mainRelative = Path.Combine(directory, $"{baseName}.jpg").Replace('\\', '/'); + var mainFull = Path.Combine(_uploadRoot, mainRelative); + Directory.CreateDirectory(Path.GetDirectoryName(mainFull)!); + await File.WriteAllBytesAsync(mainFull, mainBytes, ct); + var main = new UploadedFile(0, mainRelative); + + // ② ساخت و ذخیره بندانگشتی + var thumbBytes = await OptimizeAsync(fileBytes, ThumbMaxWidth, ThumbMaxHeight); + var thumbRelative = Path.Combine(directory, $"{baseName}_thumb.jpg").Replace('\\', '/'); + var thumbFull = Path.Combine(_uploadRoot, thumbRelative); + await File.WriteAllBytesAsync(thumbFull, thumbBytes, ct); + var thumb = new UploadedFile(0, thumbRelative); + + _logger.LogInformation( + "Image saved — Main: {MainPath} ({MainKB}KB), Thumb: {ThumbPath} ({ThumbKB}KB)", + mainRelative, mainBytes.Length / 1024, + thumbRelative, thumbBytes.Length / 1024); + + return new UploadedImage(main, thumb); + } + + // ──────────────────────────────────────────────────── + // حذف فایل از دیسک + // ──────────────────────────────────────────────────── + public Task DeleteAsync(long fileId, CancellationToken ct = default) + { + _logger.LogWarning("DeleteAsync called with fileId={Id} — file deletion by ID not supported in disk mode", fileId); + return Task.CompletedTask; + } + + // ──────────────────────────────────────────────────── + // خواندن فایل از دیسک → تبدیل به base64 data-URI + // ──────────────────────────────────────────────────── + public string ResolveImageUrl(string? path) + { + if (string.IsNullOrWhiteSpace(path)) + return string.Empty; + + // اگر از قبل data-URI یا URL مطلق هست، همان را برگردان + if (path.StartsWith("data:", StringComparison.OrdinalIgnoreCase) || + path.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || + path.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) + return path; + + try + { + var fullPath = Path.Combine(_uploadRoot, path.TrimStart('/')); + if (!File.Exists(fullPath)) + { + _logger.LogWarning("Image file not found on disk, trying FMS fallback: {Path}", fullPath); + + // ── FMS Fallback: دانلود از dl.afrino.co و کش محلی (برای مهاجرت) ── + if (!TryDownloadFromFms(path.TrimStart('/'), fullPath)) + return string.Empty; + + _logger.LogInformation("Downloaded and cached from FMS: {Path}", path); + } + + var bytes = File.ReadAllBytes(fullPath); + var mime = GetMimeFromExtension(Path.GetExtension(fullPath)); + return $"data:{mime};base64,{Convert.ToBase64String(bytes)}"; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error reading image from disk: {Path}", path); + return string.Empty; + } + } + + // ──────────────────────────────────────────────────── + // FMS Fallback — دانلود از سرور قدیمی و کش محلی (مهاجرت) + // ──────────────────────────────────────────────────── + private bool TryDownloadFromFms(string relativePath, string localPath) + { + try + { + var fmsUrl = $"{_fmsBaseUrl}/{relativePath}"; + _logger.LogInformation("Attempting FMS download: {Url}", fmsUrl); + + using var client = _httpClientFactory.CreateClient("FMS"); + using var response = client.Send(new HttpRequestMessage(HttpMethod.Get, fmsUrl), + HttpCompletionOption.ResponseHeadersRead); + + if (!response.IsSuccessStatusCode) + { + _logger.LogWarning("FMS returned {Status} for {Url}", response.StatusCode, fmsUrl); + return false; + } + + // ذخیره روی دیسک + var directory = Path.GetDirectoryName(localPath)!; + Directory.CreateDirectory(directory); + + using var responseStream = response.Content.ReadAsStream(); + using var fileStream = new FileStream(localPath, FileMode.Create, FileAccess.Write, FileShare.None); + responseStream.CopyTo(fileStream); + + _logger.LogInformation("Cached FMS file locally: {Path} ({Size} bytes)", relativePath, fileStream.Length); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to download from FMS: {Path}", relativePath); + return false; + } + } + + // ──────────────────────────────────────────────────── + // بهینه‌سازی تصویر (ریسایز + فشرده‌سازی JPEG) + // ──────────────────────────────────────────────────── + private static async Task OptimizeAsync(byte[] imageBytes, int maxWidth, int maxHeight) + { + using var image = SixLabors.ImageSharp.Image.Load(imageBytes); + + if (image.Width > maxWidth || image.Height > maxHeight) + { + image.Mutate(x => x.Resize(new ResizeOptions + { + Size = new Size(maxWidth, maxHeight), + Mode = ResizeMode.Max + })); + } + + using var ms = new MemoryStream(); + await image.SaveAsJpegAsync(ms, new JpegEncoder { Quality = JpegQuality }); + return ms.ToArray(); + } + + // ──────────────────────────────────────────────────── + // پسوند فایل از mime type + // ──────────────────────────────────────────────────── + private static string GetExtension(string mime, string? fileName) + { + if (!string.IsNullOrEmpty(fileName)) + { + var ext = Path.GetExtension(fileName); + if (!string.IsNullOrEmpty(ext)) + return ext.ToLowerInvariant(); + } + + return mime.ToLowerInvariant() switch + { + "image/jpeg" or "image/jpg" => ".jpg", + "image/png" => ".png", + "image/gif" => ".gif", + "image/webp" => ".webp", + "image/svg+xml" => ".svg", + "application/pdf" => ".pdf", + _ => ".bin" + }; + } + + private static string GetMimeFromExtension(string extension) + { + return extension.ToLowerInvariant() switch + { + ".jpg" or ".jpeg" => "image/jpeg", + ".png" => "image/png", + ".gif" => "image/gif", + ".webp" => "image/webp", + ".svg" => "image/svg+xml", + ".pdf" => "application/pdf", + _ => "application/octet-stream" + }; + } +} diff --git a/src/CMSMicroservice.Infrastructure/Services/Payment/PYMSPaymentService.cs b/src/CMSMicroservice.Infrastructure/Services/Payment/PYMSPaymentService.cs new file mode 100644 index 0000000..59dd20d --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Services/Payment/PYMSPaymentService.cs @@ -0,0 +1,284 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Protobuf.Protos.PYMS; +using CMSMicroservice.Protobuf.Protos.PYMS.Transaction; +using Grpc.Net.Client; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using System.Net.Http; + +namespace CMSMicroservice.Infrastructure.Services.Payment; + +/// +/// پیاده‌سازی درگاه پرداخت از طریق PYMS (Payment Microservice) +/// CMS به جای اتصال مستقیم به ZarinPal، از PYMS استفاده می‌کند. +/// PYMS تراکنش‌ها را ذخیره و با ZarinPal ارتباط برقرار می‌کند. +/// +public class PYMSPaymentService : IPaymentGatewayService, IDisposable +{ + private readonly ILogger _logger; + private readonly GrpcChannel _channel; + private readonly TransactionContract.TransactionContractClient _client; + private readonly string _merchantId; + private readonly bool _useSandbox; + + public PYMSPaymentService( + IConfiguration configuration, + ILogger logger) + { + _logger = logger; + + var pymsAddress = configuration["PYMS:Address"] + ?? throw new InvalidOperationException("PYMS:Address is not configured."); + + _merchantId = configuration["ZarinPal:MerchantId"] + ?? throw new InvalidOperationException("ZarinPal:MerchantId is not configured."); + + _useSandbox = configuration.GetValue("ZarinPal:UseSandbox", true); + + // ایجاد کانال gRPC به PYMS + _channel = GrpcChannel.ForAddress(pymsAddress, new GrpcChannelOptions + { + HttpHandler = new SocketsHttpHandler + { + EnableMultipleHttp2Connections = true, + PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5), + KeepAlivePingDelay = TimeSpan.FromSeconds(60), + KeepAlivePingTimeout = TimeSpan.FromSeconds(30), + } + }); + + _client = new TransactionContract.TransactionContractClient(_channel); + + _logger.LogInformation( + "PYMS Payment Service initialized. Address={Address}, Mode={Mode}", + pymsAddress, _useSandbox ? "🧪 Sandbox" : "🏦 Production"); + } + + /// + /// مرحله ۱: ارسال درخواست پرداخت به PYMS + /// PYMS تراکنش را ایجاد و URL درگاه را برمی‌گرداند + /// + public async Task InitiatePaymentAsync( + PaymentRequest request, + CancellationToken cancellationToken = default) + { + try + { + // CMS مبالغ را به تومان نگه‌داری می‌کند + // PYMS مبلغ را به ریال می‌خواهد — تبدیل تومان به ریال + var amountInRials = (long)(request.Amount * 10); + + var grpcRequest = new PaymentRequestRequest + { + MerchantId = _merchantId, + Amount = amountInRials, + CallbackUrl = request.CallbackUrl ?? string.Empty, + Description = request.Description ?? string.Empty, + OrderId = request.UserId.ToString(), + // نوع تراکنش: Sandbox برای تست، Real برای Production + Type = _useSandbox ? TransactionTypeEnum.Sandbox : TransactionTypeEnum.Real, + Currency = CurrencyEnum.Irt, // تومان + }; + + if (!string.IsNullOrWhiteSpace(request.Mobile)) + grpcRequest.Mobile = request.Mobile; + + _logger.LogInformation( + "PYMS payment request: Amount={AmountToman} Toman ({AmountRial} Rial), User={UserId}, Sandbox={Sandbox}", + request.Amount, amountInRials, request.UserId, _useSandbox); + + var response = await _client.PaymentRequestAsync(grpcRequest, cancellationToken: cancellationToken); + + if (!string.IsNullOrEmpty(response.PaymentGWUrl)) + { + _logger.LogInformation( + "PYMS payment initiated successfully: GatewayUrl={Url}", + response.PaymentGWUrl); + + // از URL درگاه، Authority را استخراج می‌کنیم (آخرین بخش URL) + var authority = ExtractAuthorityFromUrl(response.PaymentGWUrl); + + return new PaymentInitiateResult + { + IsSuccess = true, + RefId = authority, + GatewayUrl = response.PaymentGWUrl + }; + } + + _logger.LogError("PYMS payment request failed: Empty gateway URL returned"); + + return new PaymentInitiateResult + { + IsSuccess = false, + ErrorMessage = "خطا در دریافت آدرس درگاه از PYMS" + }; + } + catch (Grpc.Core.RpcException ex) + { + _logger.LogError(ex, "PYMS gRPC error in InitiatePayment: Status={Status}, Detail={Detail}", + ex.StatusCode, ex.Status.Detail); + + return new PaymentInitiateResult + { + IsSuccess = false, + ErrorMessage = $"خطا در ارتباط با سرویس پرداخت: {ex.Status.Detail}" + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "PYMS InitiatePayment exception"); + return new PaymentInitiateResult + { + IsSuccess = false, + ErrorMessage = $"خطا در ارتباط با سرویس پرداخت: {ex.Message}" + }; + } + } + + /// + /// تأیید پرداخت بدون مبلغ — PYMS خودش مبلغ را از تراکنش ذخیره‌شده می‌خواند + /// + public async Task VerifyPaymentAsync( + string refId, + string verificationToken, + CancellationToken cancellationToken = default) + { + return await VerifyPaymentInternalAsync(refId, verificationToken, cancellationToken); + } + + /// + /// تأیید پرداخت با مبلغ — PYMS خودش verify را انجام می‌دهد + /// refId = Authority, verificationToken = Status (OK/NOK) + /// + public async Task VerifyPaymentAsync( + string refId, + string verificationToken, + decimal amountInToman, + CancellationToken cancellationToken = default) + { + return await VerifyPaymentInternalAsync(refId, verificationToken, cancellationToken); + } + + private async Task VerifyPaymentInternalAsync( + string refId, + string verificationToken, + CancellationToken cancellationToken) + { + try + { + // اگر کاربر لغو کرده + if (!string.Equals(verificationToken, "OK", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogWarning("Payment cancelled by user: Authority={Authority}", refId); + return new PaymentVerificationResult + { + IsSuccess = false, + RefId = refId, + Message = "پرداخت توسط کاربر لغو شد" + }; + } + + var grpcRequest = new PaymentVerificationRequest + { + Authority = refId, + Status = verificationToken + }; + + _logger.LogInformation("PYMS verify request: Authority={Authority}, Status={Status}", + refId, verificationToken); + + var response = await _client.PaymentVerificationAsync(grpcRequest, cancellationToken: cancellationToken); + + if (response.PaymentStatus) + { + _logger.LogInformation( + "PYMS payment verified: Id={Id}, RefId={RefId}, OrderId={OrderId}, StatusCode={StatusCode}", + response.Id, response.RefId, response.OrderId, response.VerificationStatusCode); + + return new PaymentVerificationResult + { + IsSuccess = true, + RefId = refId, + TrackingCode = response.RefId, + Amount = 0, // مبلغ از DB خوانده می‌شود + Message = response.Message ?? "تراکنش موفق" + }; + } + + _logger.LogError( + "PYMS verify failed: Authority={Authority}, StatusCode={StatusCode}, Message={Message}", + refId, response.VerificationStatusCode, response.Message); + + return new PaymentVerificationResult + { + IsSuccess = false, + RefId = refId, + Message = response.Message ?? "تأیید پرداخت ناموفق" + }; + } + catch (Grpc.Core.RpcException ex) + { + _logger.LogError(ex, "PYMS gRPC error in VerifyPayment: Status={Status}, Detail={Detail}", + ex.StatusCode, ex.Status.Detail); + + return new PaymentVerificationResult + { + IsSuccess = false, + RefId = refId, + Message = $"خطا در تأیید تراکنش: {ex.Status.Detail}" + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "PYMS VerifyPayment exception: Authority={Authority}", refId); + return new PaymentVerificationResult + { + IsSuccess = false, + RefId = refId, + Message = $"خطا در تأیید تراکنش: {ex.Message}" + }; + } + } + + /// + /// PYMS فعلاً قابلیت Payout ندارد + /// + public Task ProcessPayoutAsync( + PayoutRequest request, + CancellationToken cancellationToken = default) + { + _logger.LogWarning("PYMS does not support direct payout yet."); + return Task.FromResult(new PayoutResult + { + IsSuccess = false, + Message = "سرویس پرداخت (PYMS) فعلاً از قابلیت واریز مستقیم پشتیبانی نمی‌کند", + ProcessedAt = DateTime.UtcNow + }); + } + + /// + /// استخراج Authority از URL درگاه + /// مثال: https://sandbox.zarinpal.com/pg/StartPay/A00000000000000000000000000123456789 → A00000000000000000000000000123456789 + /// + private static string ExtractAuthorityFromUrl(string gatewayUrl) + { + if (string.IsNullOrEmpty(gatewayUrl)) + return string.Empty; + + // Authority معمولاً آخرین بخش URL است + var uri = new Uri(gatewayUrl); + var segments = uri.Segments; + if (segments.Length > 0) + { + return segments[^1].TrimEnd('/'); + } + + return gatewayUrl; + } + + public void Dispose() + { + _channel?.Dispose(); + } +} diff --git a/src/CMSMicroservice.Infrastructure/Services/Payment/ZarinPalPaymentService.cs b/src/CMSMicroservice.Infrastructure/Services/Payment/ZarinPalPaymentService.cs new file mode 100644 index 0000000..1cd9df3 --- /dev/null +++ b/src/CMSMicroservice.Infrastructure/Services/Payment/ZarinPalPaymentService.cs @@ -0,0 +1,358 @@ +using System.Net.Http; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using CMSMicroservice.Application.Common.Interfaces; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.Infrastructure.Services.Payment; + +/// +/// پیاده‌سازی درگاه پرداخت زرین‌پال +/// ساپورت Sandbox (تست) و Production +/// +public class ZarinPalPaymentService : IPaymentGatewayService +{ + private readonly HttpClient _httpClient; + private readonly ILogger _logger; + private readonly string _merchantId; + private readonly bool _useSandbox; + + // آدرس‌های Production + private const string ProductionApiBase = "https://api.zarinpal.com"; + private const string ProductionStartPayBase = "https://www.zarinpal.com"; + + // آدرس‌های Sandbox + private const string SandboxApiBase = "https://sandbox.zarinpal.com"; + private const string SandboxStartPayBase = "https://sandbox.zarinpal.com"; + + // مسیرهای API (مشترک) + private const string RequestEndpoint = "/pg/v4/payment/request.json"; + private const string VerifyEndpoint = "/pg/v4/payment/verify.json"; + private const string StartPayPath = "/pg/StartPay/"; + + private static readonly JsonSerializerOptions JsonOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + + public ZarinPalPaymentService( + HttpClient httpClient, + IConfiguration configuration, + ILogger logger) + { + _httpClient = httpClient; + _logger = logger; + + _merchantId = configuration["ZarinPal:MerchantId"] + ?? throw new InvalidOperationException("ZarinPal:MerchantId is not configured."); + _useSandbox = configuration.GetValue("ZarinPal:UseSandbox", true); + + var apiBase = _useSandbox ? SandboxApiBase : ProductionApiBase; + _httpClient.BaseAddress = new Uri(apiBase); + + _logger.LogInformation("ZarinPal payment service initialized. Mode: {Mode}", + _useSandbox ? "🧪 Sandbox" : "🏦 Production"); + } + + /// + /// مرحله ۱: ارسال درخواست پرداخت به زرین‌پال و دریافت Authority + /// + public async Task InitiatePaymentAsync( + PaymentRequest request, + CancellationToken cancellationToken = default) + { + try + { + // زرین‌پال مبلغ را به ریال می‌خواهد — تبدیل تومان به ریال + var amountInRials = (long)(request.Amount * 10); + + var zarinPalRequest = new ZarinPalPaymentRequest + { + MerchantId = _merchantId, + Amount = amountInRials, + Description = request.Description, + CallbackUrl = request.CallbackUrl, + Metadata = new ZarinPalMetadata + { + Mobile = string.IsNullOrWhiteSpace(request.Mobile) ? null : request.Mobile + } + }; + + var jsonContent = JsonSerializer.Serialize(zarinPalRequest, JsonOptions); + var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); + + _logger.LogInformation( + "ZarinPal payment request: Amount={AmountToman} Toman ({AmountRial} Rial), User={UserId}, Sandbox={Sandbox}", + request.Amount, amountInRials, request.UserId, _useSandbox); + + var response = await _httpClient.PostAsync(RequestEndpoint, content, cancellationToken); + var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); + + _logger.LogDebug("ZarinPal request response: {StatusCode} - {Body}", + response.StatusCode, responseBody); + + var result = JsonSerializer.Deserialize(responseBody, JsonOptions); + + if (result?.Data?.Code == 100 && !string.IsNullOrEmpty(result.Data.Authority)) + { + var startPayBase = _useSandbox ? SandboxStartPayBase : ProductionStartPayBase; + var gatewayUrl = $"{startPayBase}{StartPayPath}{result.Data.Authority}"; + + _logger.LogInformation( + "ZarinPal payment initiated successfully: Authority={Authority}, GatewayUrl={Url}", + result.Data.Authority, gatewayUrl); + + return new PaymentInitiateResult + { + IsSuccess = true, + RefId = result.Data.Authority, + GatewayUrl = gatewayUrl + }; + } + + // خطا + var errorCode = result?.Errors?.Code ?? result?.Data?.Code ?? -1; + var errorMessage = result?.Errors?.Message ?? "خطای ناشناخته از زرین‌پال"; + + _logger.LogError( + "ZarinPal payment request failed: Code={Code}, Message={Message}", + errorCode, errorMessage); + + return new PaymentInitiateResult + { + IsSuccess = false, + ErrorMessage = $"خطای درگاه زرین‌پال (کد {errorCode}): {errorMessage}" + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "ZarinPal InitiatePayment exception"); + return new PaymentInitiateResult + { + IsSuccess = false, + ErrorMessage = $"خطا در ارتباط با درگاه زرین‌پال: {ex.Message}" + }; + } + } + + /// + /// تأیید پرداخت بدون مبلغ — برای سازگاری با اینترفیس. + /// ⚠ زرین‌پال مبلغ را در Verify نیاز دارد. از overload با amount استفاده کنید. + /// + public Task VerifyPaymentAsync( + string refId, + string verificationToken, + CancellationToken cancellationToken = default) + { + _logger.LogWarning("ZarinPal VerifyPaymentAsync called without amount — verification may fail!"); + return VerifyPaymentWithAmountAsync(refId, verificationToken, 0, cancellationToken); + } + + /// + /// تأیید پرداخت با مبلغ — نسخه اصلی برای زرین‌پال + /// refId = Authority، verificationToken = Status (OK/NOK)، amountInToman = مبلغ به تومان + /// + public Task VerifyPaymentAsync( + string refId, + string verificationToken, + decimal amountInToman, + CancellationToken cancellationToken = default) + { + return VerifyPaymentWithAmountAsync(refId, verificationToken, amountInToman, cancellationToken); + } + + private async Task VerifyPaymentWithAmountAsync( + string refId, + string verificationToken, + decimal amountInToman, + CancellationToken cancellationToken) + { + try + { + // verificationToken باید "OK" باشد — در غیر اینصورت کاربر لغو کرده + if (!string.Equals(verificationToken, "OK", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogWarning("ZarinPal payment cancelled by user: Authority={Authority}", refId); + return new PaymentVerificationResult + { + IsSuccess = false, + RefId = refId, + Message = "پرداخت توسط کاربر لغو شد" + }; + } + + // تبدیل تومان → ریال (×۱۰) + var amountInRials = (long)(amountInToman * 10); + + var verifyRequest = new ZarinPalVerifyRequest + { + MerchantId = _merchantId, + Authority = refId, + Amount = amountInRials + }; + + var jsonContent = JsonSerializer.Serialize(verifyRequest, JsonOptions); + var content = new StringContent(jsonContent, Encoding.UTF8, "application/json"); + + _logger.LogInformation("ZarinPal verify request: Authority={Authority}, Amount={Amount} Rial", + refId, amountInRials); + + var response = await _httpClient.PostAsync(VerifyEndpoint, content, cancellationToken); + var responseBody = await response.Content.ReadAsStringAsync(cancellationToken); + + _logger.LogDebug("ZarinPal verify response: {StatusCode} - {Body}", + response.StatusCode, responseBody); + + var result = JsonSerializer.Deserialize(responseBody, JsonOptions); + + // code 100 = موفق | code 101 = قبلاً تأیید شده + if (result?.Data?.Code is 100 or 101) + { + _logger.LogInformation( + "ZarinPal payment verified: Authority={Authority}, RefId={RefId}, CardPan={CardPan}", + refId, result.Data.RefId, result.Data.CardPan); + + return new PaymentVerificationResult + { + IsSuccess = true, + RefId = refId, + TrackingCode = result.Data.RefId?.ToString(), + Amount = (result.Data.Amount ?? 0) / 10m, // ریال → تومان + Message = result.Data.Code == 101 + ? "تراکنش قبلاً تأیید شده" + : "تراکنش موفق" + }; + } + + var errorCode = result?.Errors?.Code ?? result?.Data?.Code ?? -1; + var errorMessage = result?.Errors?.Message ?? "تأیید تراکنش ناموفق"; + + _logger.LogError( + "ZarinPal verify failed: Authority={Authority}, Code={Code}, Message={Message}", + refId, errorCode, errorMessage); + + return new PaymentVerificationResult + { + IsSuccess = false, + RefId = refId, + Message = $"تأیید پرداخت ناموفق (کد {errorCode}): {errorMessage}" + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "ZarinPal VerifyPayment exception: Authority={Authority}", refId); + return new PaymentVerificationResult + { + IsSuccess = false, + RefId = refId, + Message = $"خطا در تأیید تراکنش: {ex.Message}" + }; + } + } + + /// + /// زرین‌پال Payout مستقیم ندارد — این متد NotSupported برمی‌گرداند + /// برای Payout باید از سرویس دیگری (مثل دایا) استفاده شود + /// + public Task ProcessPayoutAsync( + PayoutRequest request, + CancellationToken cancellationToken = default) + { + _logger.LogWarning("ZarinPal does not support direct payout. Use a different provider for payouts."); + return Task.FromResult(new PayoutResult + { + IsSuccess = false, + Message = "درگاه زرین‌پال از قابلیت واریز مستقیم پشتیبانی نمی‌کند", + ProcessedAt = DateTime.UtcNow + }); + } + + // ── ZarinPal Request/Response DTOs ── + + private class ZarinPalPaymentRequest + { + public string MerchantId { get; set; } = string.Empty; + public long Amount { get; set; } + public string Description { get; set; } = string.Empty; + public string CallbackUrl { get; set; } = string.Empty; + public ZarinPalMetadata? Metadata { get; set; } + } + + private class ZarinPalMetadata + { + public string? Mobile { get; set; } + public string? Email { get; set; } + } + + private class ZarinPalVerifyRequest + { + public string MerchantId { get; set; } = string.Empty; + public long Amount { get; set; } + public string Authority { get; set; } = string.Empty; + } + + private class ZarinPalResponse + { + public ZarinPalResponseData? Data { get; set; } + + [JsonConverter(typeof(ZarinPalErrorsConverter))] + public ZarinPalResponseErrors? Errors { get; set; } + } + + private class ZarinPalResponseData + { + public int? Code { get; set; } + public string? Message { get; set; } + public string? Authority { get; set; } + public long? RefId { get; set; } + public long? Amount { get; set; } + public string? CardPan { get; set; } + public string? CardHash { get; set; } + public string? FeeType { get; set; } + public long? Fee { get; set; } + } + + private class ZarinPalResponseErrors + { + public int? Code { get; set; } + public string? Message { get; set; } + } + + /// + /// ZarinPal returns errors as [] (empty array) when no error, or as {...} object when there's an error. + /// This converter handles both cases. + /// + private class ZarinPalErrorsConverter : JsonConverter + { + public override ZarinPalResponseErrors? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.StartArray) + { + // Skip the empty array [] + while (reader.Read() && reader.TokenType != JsonTokenType.EndArray) { } + return null; + } + + if (reader.TokenType == JsonTokenType.StartObject) + { + return JsonSerializer.Deserialize(ref reader); + } + + if (reader.TokenType == JsonTokenType.Null) + { + return null; + } + + reader.Skip(); + return null; + } + + public override void Write(Utf8JsonWriter writer, ZarinPalResponseErrors? value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value, options); + } + } +} diff --git a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj index 7e6e219..27ba442 100644 --- a/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj +++ b/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj @@ -66,6 +66,16 @@ + + + + + + + + + + diff --git a/src/CMSMicroservice.Protobuf/Protos/blogcategory.proto b/src/CMSMicroservice.Protobuf/Protos/blogcategory.proto new file mode 100644 index 0000000..0143fff --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/blogcategory.proto @@ -0,0 +1,115 @@ +syntax = "proto3"; + +package blogcategory; + +import "public_messages.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.BlogCategory"; + +service BlogCategoryContract +{ + rpc CreateBlogCategory(CreateBlogCategoryRequest) returns (CreateBlogCategoryResponse){ + option (google.api.http) = { post: "/CreateBlogCategory" body: "*" }; + }; + rpc UpdateBlogCategory(UpdateBlogCategoryRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { put: "/UpdateBlogCategory" body: "*" }; + }; + rpc DeleteBlogCategory(DeleteBlogCategoryRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { delete: "/DeleteBlogCategory" body: "*" }; + }; + rpc GetBlogCategory(GetBlogCategoryRequest) returns (GetBlogCategoryResponse){ + option (google.api.http) = { get: "/GetBlogCategory" }; + }; + rpc GetAllBlogCategories(GetAllBlogCategoriesRequest) returns (GetAllBlogCategoriesResponse){ + option (google.api.http) = { get: "/GetAllBlogCategories" }; + }; + rpc GetActiveBlogCategories(GetActiveBlogCategoriesRequest) returns (GetActiveBlogCategoriesResponse){ + option (google.api.http) = { get: "/GetActiveBlogCategories" }; + }; +} + +// ── Create ── +message CreateBlogCategoryRequest +{ + string title = 1; + string slug = 2; + google.protobuf.StringValue description = 3; + google.protobuf.StringValue icon_name = 4; + int32 sort_order = 5; + bool is_active = 6; +} +message CreateBlogCategoryResponse +{ + int64 id = 1; +} + +// ── Update ── +message UpdateBlogCategoryRequest +{ + int64 id = 1; + string title = 2; + string slug = 3; + google.protobuf.StringValue description = 4; + google.protobuf.StringValue icon_name = 5; + int32 sort_order = 6; + bool is_active = 7; +} + +// ── Delete ── +message DeleteBlogCategoryRequest +{ + int64 id = 1; +} + +// ── Get ── +message GetBlogCategoryRequest +{ + int64 id = 1; +} +message GetBlogCategoryResponse +{ + int64 id = 1; + string title = 2; + string slug = 3; + google.protobuf.StringValue description = 4; + google.protobuf.StringValue icon_name = 5; + int32 sort_order = 6; + bool is_active = 7; + int32 post_count = 8; + google.protobuf.Timestamp created = 9; +} + +// ── Get All (Admin) ── +message GetAllBlogCategoriesRequest +{ + messages.PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; +} +message GetAllBlogCategoriesResponse +{ + messages.MetaData meta_data = 1; + repeated BlogCategoryListItem models = 2; +} +message BlogCategoryListItem +{ + int64 id = 1; + string title = 2; + string slug = 3; + google.protobuf.StringValue description = 4; + google.protobuf.StringValue icon_name = 5; + int32 sort_order = 6; + bool is_active = 7; + int32 post_count = 8; +} + +// ── Get Active (Customer) ── +message GetActiveBlogCategoriesRequest {} +message GetActiveBlogCategoriesResponse +{ + repeated BlogCategoryListItem categories = 1; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/blogpost.proto b/src/CMSMicroservice.Protobuf/Protos/blogpost.proto new file mode 100644 index 0000000..22127de --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/blogpost.proto @@ -0,0 +1,221 @@ +syntax = "proto3"; + +package blogpost; + +import "public_messages.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.BlogPost"; + +service BlogPostContract +{ + rpc CreateBlogPost(CreateBlogPostRequest) returns (CreateBlogPostResponse){ + option (google.api.http) = { post: "/CreateBlogPost" body: "*" }; + }; + rpc UpdateBlogPost(UpdateBlogPostRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { put: "/UpdateBlogPost" body: "*" }; + }; + rpc DeleteBlogPost(DeleteBlogPostRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { delete: "/DeleteBlogPost" body: "*" }; + }; + rpc GetBlogPost(GetBlogPostRequest) returns (GetBlogPostResponse){ + option (google.api.http) = { get: "/GetBlogPost" }; + }; + rpc GetBlogPostBySlug(GetBlogPostBySlugRequest) returns (GetBlogPostResponse){ + option (google.api.http) = { get: "/GetBlogPostBySlug" }; + }; + rpc GetAllBlogPosts(GetAllBlogPostsRequest) returns (GetAllBlogPostsResponse){ + option (google.api.http) = { get: "/GetAllBlogPosts" }; + }; + rpc GetPublishedBlogPosts(GetPublishedBlogPostsRequest) returns (GetAllBlogPostsResponse){ + option (google.api.http) = { get: "/GetPublishedBlogPosts" }; + }; + rpc GetFeaturedBlogPosts(GetFeaturedBlogPostsRequest) returns (GetAllBlogPostsResponse){ + option (google.api.http) = { get: "/GetFeaturedBlogPosts" }; + }; + rpc PublishBlogPost(PublishBlogPostRequest) returns (PublishBlogPostResponse){ + option (google.api.http) = { post: "/PublishBlogPost" body: "*" }; + }; + rpc ArchiveBlogPost(ArchiveBlogPostRequest) returns (ArchiveBlogPostResponse){ + option (google.api.http) = { post: "/ArchiveBlogPost" body: "*" }; + }; + rpc IncrementViewCount(IncrementViewCountRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { post: "/IncrementViewCount" body: "*" }; + }; +} + +// ── Create ── +message CreateBlogPostRequest +{ + string title = 1; + string slug = 2; + google.protobuf.StringValue summary = 3; + string html_content = 4; + google.protobuf.StringValue featured_image_path = 5; + google.protobuf.StringValue featured_image_thumbnail_path = 6; + repeated int64 category_ids = 7; + repeated int64 tag_ids = 8; + bool is_featured = 9; + int32 sort_order = 10; + BlogImageFileModel image_file = 11; +} +message CreateBlogPostResponse +{ + int64 id = 1; +} + +// ── Update ── +message UpdateBlogPostRequest +{ + int64 id = 1; + string title = 2; + string slug = 3; + google.protobuf.StringValue summary = 4; + string html_content = 5; + google.protobuf.StringValue featured_image_path = 6; + google.protobuf.StringValue featured_image_thumbnail_path = 7; + repeated int64 category_ids = 8; + repeated int64 tag_ids = 9; + bool is_featured = 10; + int32 sort_order = 11; + BlogImageFileModel image_file = 12; +} + +// ── Delete ── +message DeleteBlogPostRequest +{ + int64 id = 1; +} + +// ── Get Single ── +message GetBlogPostRequest +{ + int64 id = 1; +} +message GetBlogPostBySlugRequest +{ + string slug = 1; +} +message GetBlogPostResponse +{ + int64 id = 1; + string title = 2; + string slug = 3; + google.protobuf.StringValue summary = 4; + string html_content = 5; + google.protobuf.StringValue featured_image_path = 6; + google.protobuf.StringValue featured_image_thumbnail_path = 7; + int32 status = 8; + string status_name = 9; + google.protobuf.Timestamp published_at = 10; + int32 view_count = 11; + int64 author_user_id = 12; + bool is_featured = 13; + int32 sort_order = 14; + google.protobuf.Timestamp created = 15; + google.protobuf.Timestamp last_modified = 16; + repeated BlogPostCategoryInfo categories = 17; + repeated BlogPostTagInfo tags = 18; +} +message BlogPostCategoryInfo +{ + int64 id = 1; + string title = 2; + string slug = 3; +} +message BlogPostTagInfo +{ + int64 id = 1; + string title = 2; + string name = 3; +} + +// ── Get All (Admin) ── +message GetAllBlogPostsRequest +{ + messages.PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetAllBlogPostsFilter filter = 3; +} +message GetAllBlogPostsFilter +{ + google.protobuf.StringValue search_term = 1; + google.protobuf.Int32Value status = 2; + google.protobuf.Int64Value category_id = 3; + google.protobuf.BoolValue is_featured = 4; +} +message GetAllBlogPostsResponse +{ + messages.MetaData meta_data = 1; + repeated BlogPostListItem models = 2; +} +message BlogPostListItem +{ + int64 id = 1; + string title = 2; + string slug = 3; + google.protobuf.StringValue summary = 4; + google.protobuf.StringValue featured_image_thumbnail_path = 5; + int32 status = 6; + string status_name = 7; + google.protobuf.Timestamp published_at = 8; + int32 view_count = 9; + bool is_featured = 10; + google.protobuf.Timestamp created = 11; + repeated BlogPostCategoryInfo categories = 12; +} + +// ── Get Published (Customer) ── +message GetPublishedBlogPostsRequest +{ + messages.PaginationState pagination_state = 1; + google.protobuf.StringValue search_term = 2; + google.protobuf.Int64Value category_id = 3; +} + +// ── Get Featured ── +message GetFeaturedBlogPostsRequest +{ + int32 count = 1; +} + +// ── Publish ── +message PublishBlogPostRequest +{ + int64 id = 1; +} +message PublishBlogPostResponse +{ + bool success = 1; + string message = 2; + google.protobuf.Timestamp published_at = 3; +} + +// ── Archive ── +message ArchiveBlogPostRequest +{ + int64 id = 1; +} +message ArchiveBlogPostResponse +{ + bool success = 1; + string message = 2; +} + +// ── View Count ── +message IncrementViewCountRequest +{ + int64 id = 1; +} + +// ── File upload model for binary image uploads from BackOffice ── +message BlogImageFileModel +{ + bytes file = 1; + string mime = 2; + string file_name = 3; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/blogpostimage.proto b/src/CMSMicroservice.Protobuf/Protos/blogpostimage.proto new file mode 100644 index 0000000..5dab1e4 --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/blogpostimage.proto @@ -0,0 +1,80 @@ +syntax = "proto3"; + +package blogpostimage; + +import "public_messages.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.BlogPostImage"; + +service BlogPostImageContract +{ + rpc AddBlogPostImage(AddBlogPostImageRequest) returns (AddBlogPostImageResponse){ + option (google.api.http) = { post: "/AddBlogPostImage" body: "*" }; + }; + rpc DeleteBlogPostImage(DeleteBlogPostImageRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { delete: "/DeleteBlogPostImage" body: "*" }; + }; + rpc GetBlogPostImages(GetBlogPostImagesRequest) returns (GetBlogPostImagesResponse){ + option (google.api.http) = { get: "/GetBlogPostImages" }; + }; + rpc ReorderBlogPostImages(ReorderBlogPostImagesRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { put: "/ReorderBlogPostImages" body: "*" }; + }; +} + +// ── Add ── +message AddBlogPostImageRequest +{ + int64 blog_post_id = 1; + string image_path = 2; + string thumbnail_path = 3; + google.protobuf.StringValue alt_text = 4; + google.protobuf.StringValue caption = 5; + int32 sort_order = 6; +} +message AddBlogPostImageResponse +{ + int64 id = 1; +} + +// ── Delete ── +message DeleteBlogPostImageRequest +{ + int64 id = 1; +} + +// ── Get All for Post ── +message GetBlogPostImagesRequest +{ + int64 blog_post_id = 1; +} +message GetBlogPostImagesResponse +{ + repeated BlogPostImageItem images = 1; +} +message BlogPostImageItem +{ + int64 id = 1; + int64 blog_post_id = 2; + string image_path = 3; + string thumbnail_path = 4; + google.protobuf.StringValue alt_text = 5; + google.protobuf.StringValue caption = 6; + int32 sort_order = 7; +} + +// ── Reorder ── +message ReorderBlogPostImagesRequest +{ + repeated ImageSortItem items = 1; +} +message ImageSortItem +{ + int64 id = 1; + int32 sort_order = 2; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/category.proto b/src/CMSMicroservice.Protobuf/Protos/category.proto index 8a3d500..ed6cfdb 100644 --- a/src/CMSMicroservice.Protobuf/Protos/category.proto +++ b/src/CMSMicroservice.Protobuf/Protos/category.proto @@ -136,6 +136,8 @@ message GetAllCategoryByFilterResponseModel google.protobuf.Int64Value parent_id = 6; bool is_active = 7; int32 sort_order = 8; + // تعداد محصولات این دسته‌بندی + int32 product_count = 9; } message GetAllCategoriesRequest { messages.PaginationState pagination_state = 1; diff --git a/src/CMSMicroservice.Protobuf/Protos/commission.proto b/src/CMSMicroservice.Protobuf/Protos/commission.proto index 2c5a2b1..5cf3999 100644 --- a/src/CMSMicroservice.Protobuf/Protos/commission.proto +++ b/src/CMSMicroservice.Protobuf/Protos/commission.proto @@ -348,8 +348,8 @@ message UserWeeklyBalanceModel // GetAllWeeklyPools Query message GetAllWeeklyPoolsRequest { - google.protobuf.StringValue from_week = 1; // Format: "YYYY-Www" (optional) - google.protobuf.StringValue to_week = 2; // Format: "YYYY-Www" (optional) + google.protobuf.Int64Value from_week_definition_id = 1; // WeekDefinitionId filter (optional) + google.protobuf.Int64Value to_week_definition_id = 2; // WeekDefinitionId filter (optional) google.protobuf.BoolValue only_calculated = 3; // Only show calculated pools int32 page_index = 4; int32 page_size = 5; diff --git a/src/CMSMicroservice.Protobuf/Protos/discountorder.proto b/src/CMSMicroservice.Protobuf/Protos/discountorder.proto index 7d66400..5e8e362 100644 --- a/src/CMSMicroservice.Protobuf/Protos/discountorder.proto +++ b/src/CMSMicroservice.Protobuf/Protos/discountorder.proto @@ -158,6 +158,9 @@ message OrderItemDto int64 total_price = 6; int64 discount_amount = 7; int64 final_price = 8; + // آدرس تصویر محصول + string image_path = 9; + string thumbnail_path = 10; } // Get User Orders diff --git a/src/CMSMicroservice.Protobuf/Protos/imageresolver.proto b/src/CMSMicroservice.Protobuf/Protos/imageresolver.proto new file mode 100644 index 0000000..e4c627b --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/imageresolver.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package imageresolver; + +import "google/api/annotations.proto"; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.ImageResolver"; + +service ImageResolverContract +{ + // تبدیل لیست مسیرهای تصویر به base64 data-URI + rpc ResolveImages(ResolveImagesRequest) returns (ResolveImagesResponse){ + option (google.api.http) = { post: "/ResolveImages" body: "*" }; + }; +} + +message ResolveImagesRequest +{ + repeated string paths = 1; +} + +message ResolveImagesResponse +{ + repeated ResolvedImage images = 1; +} + +message ResolvedImage +{ + string original_path = 1; + string data_uri = 2; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/otptoken.proto b/src/CMSMicroservice.Protobuf/Protos/otptoken.proto index 0c787e1..5d58eec 100644 --- a/src/CMSMicroservice.Protobuf/Protos/otptoken.proto +++ b/src/CMSMicroservice.Protobuf/Protos/otptoken.proto @@ -36,6 +36,7 @@ message CreateNewOtpTokenRequest { string mobile = 1; string purpose = 2; + google.protobuf.StringValue sign_guid = 3; } message CreateNewOtpTokenResponse { diff --git a/src/CMSMicroservice.Protobuf/Protos/products.proto b/src/CMSMicroservice.Protobuf/Protos/products.proto index 553ad78..9acbae2 100644 --- a/src/CMSMicroservice.Protobuf/Protos/products.proto +++ b/src/CMSMicroservice.Protobuf/Protos/products.proto @@ -228,6 +228,7 @@ message GetAllProductsByFilterFilter google.protobuf.Int32Value view_count = 12; google.protobuf.Int32Value remaining_count = 13; google.protobuf.Int64Value category_id = 14; + google.protobuf.BoolValue is_active = 15; } message GetAllProductsByFilterResponse { @@ -251,6 +252,8 @@ message GetAllProductsByFilterResponseModel int32 remaining_count = 13; // لیست شناسه دسته‌بندی‌های محصول repeated int64 category_ids = 14; + // وضعیت فعال/غیرفعال (معکوس IsDeleted) + bool is_active = 15; } message GetCustomerProductsByFilterResponse diff --git a/src/CMSMicroservice.Protobuf/Protos/pyms/pyms_public_messages.proto b/src/CMSMicroservice.Protobuf/Protos/pyms/pyms_public_messages.proto new file mode 100644 index 0000000..5fa68f3 --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/pyms/pyms_public_messages.proto @@ -0,0 +1,41 @@ +syntax = "proto3"; + +package pyms_messages; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.PYMS"; + +service PYMSPublicMessageContract{} + +message PaginationState +{ + int32 page_number = 1; + int32 page_size = 2; +} + +message MetaData +{ + int64 current_page = 1; + int64 total_page = 2; + int64 page_size = 3; + int64 total_count = 4; + bool has_previous = 5; + bool has_next = 6; +} + +message DecimalValue +{ + int64 units = 1; + sfixed32 nanos = 2; +} + +enum TransactionTypeEnum +{ + Real = 0; + Sandbox = 1; +} + +enum CurrencyEnum +{ + IRR = 0; + IRT = 1; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/pyms/pyms_transaction.proto b/src/CMSMicroservice.Protobuf/Protos/pyms/pyms_transaction.proto new file mode 100644 index 0000000..1fcb587 --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/pyms/pyms_transaction.proto @@ -0,0 +1,279 @@ +syntax = "proto3"; + +package pyms_transaction; + +import "pyms/pyms_public_messages.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.PYMS.Transaction"; + +service TransactionContract +{ + rpc CreateNewTransaction(CreateNewTransactionRequest) returns (CreateNewTransactionResponse){ + option (google.api.http) = { + post: "/CreateNewTransaction" + body: "*" + }; + }; + rpc UpdateTransaction(UpdateTransactionRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + put: "/UpdateTransaction" + body: "*" + }; + }; + rpc DeleteTransaction(DeleteTransactionRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { + delete: "/DeleteTransaction" + body: "*" + }; + }; + rpc GetTransaction(GetTransactionRequest) returns (GetTransactionResponse){ + option (google.api.http) = { + get: "/GetTransaction" + }; + }; + rpc GetAllTransactionByFilter(GetAllTransactionByFilterRequest) returns (GetAllTransactionByFilterResponse){ + option (google.api.http) = { + get: "/GetAllTransactionByFilter" + }; + }; + rpc PaymentRequest(PaymentRequestRequest) returns (PaymentRequestResponse){ + option (google.api.http) = { + post: "/PaymentRequest" + body: "*" + }; + }; + rpc PaymentVerification(PaymentVerificationRequest) returns (PaymentVerificationResponse){ + option (google.api.http) = { + post: "/PaymentVerification" + body: "*" + }; + }; +} + +message CreateNewTransactionRequest +{ + string merchant_id = 1; + int64 amount = 2; + string callback_url = 3; + string description = 4; + google.protobuf.StringValue mobile = 5; + google.protobuf.StringValue email = 6; + google.protobuf.Int32Value request_status_code = 7; + google.protobuf.StringValue request_status_message = 8; + google.protobuf.StringValue authority = 9; + google.protobuf.StringValue fee_type = 10; + google.protobuf.Int64Value fee = 11; + oneof Currency_item + { + pyms_messages.CurrencyEnum currency = 12; + } + bool payment_status = 13; + google.protobuf.Int32Value verification_status_code = 14; + google.protobuf.StringValue verification_status_message = 15; + google.protobuf.StringValue card_hash = 16; + google.protobuf.StringValue card_pan = 17; + google.protobuf.StringValue ref_id = 18; + google.protobuf.StringValue order_id = 19; + oneof Type_item + { + pyms_messages.TransactionTypeEnum type = 20; + } +} + +message CreateNewTransactionResponse +{ + int64 id = 1; +} + +message UpdateTransactionRequest +{ + int64 id = 1; + string merchant_id = 2; + int64 amount = 3; + string callback_url = 4; + string description = 5; + google.protobuf.StringValue mobile = 6; + google.protobuf.StringValue email = 7; + google.protobuf.Int32Value request_status_code = 8; + google.protobuf.StringValue request_status_message = 9; + google.protobuf.StringValue authority = 10; + google.protobuf.StringValue fee_type = 11; + google.protobuf.Int64Value fee = 12; + oneof Currency_item + { + pyms_messages.CurrencyEnum currency = 13; + } + bool payment_status = 14; + google.protobuf.Int32Value verification_status_code = 15; + google.protobuf.StringValue verification_status_message = 16; + google.protobuf.StringValue card_hash = 17; + google.protobuf.StringValue card_pan = 18; + google.protobuf.StringValue ref_id = 19; + google.protobuf.StringValue order_id = 20; + oneof Type_item + { + pyms_messages.TransactionTypeEnum type = 21; + } +} + +message DeleteTransactionRequest +{ + int64 id = 1; +} + +message GetTransactionRequest +{ + google.protobuf.Int64Value id = 1; + google.protobuf.StringValue authority = 2; +} + +message GetTransactionResponse +{ + int64 id = 1; + string merchant_id = 2; + int64 amount = 3; + string callback_url = 4; + string description = 5; + google.protobuf.StringValue mobile = 6; + google.protobuf.StringValue email = 7; + google.protobuf.Int32Value request_status_code = 8; + google.protobuf.StringValue request_status_message = 9; + google.protobuf.StringValue authority = 10; + google.protobuf.StringValue fee_type = 11; + google.protobuf.Int64Value fee = 12; + oneof Currency_item + { + pyms_messages.CurrencyEnum currency = 13; + } + bool payment_status = 14; + google.protobuf.Int32Value verification_status_code = 15; + google.protobuf.StringValue verification_status_message = 16; + google.protobuf.StringValue card_hash = 17; + google.protobuf.StringValue card_pan = 18; + google.protobuf.StringValue ref_id = 19; + google.protobuf.StringValue order_id = 20; + oneof Type_item + { + pyms_messages.TransactionTypeEnum type = 21; + } +} + +message GetAllTransactionByFilterRequest +{ + pyms_messages.PaginationState pagination_state = 1; + google.protobuf.StringValue sort_by = 2; + GetAllTransactionByFilterFilter filter = 3; +} + +message GetAllTransactionByFilterFilter +{ + google.protobuf.Int64Value id = 1; + google.protobuf.StringValue merchant_id = 2; + google.protobuf.Int64Value amount = 3; + google.protobuf.StringValue callback_url = 4; + google.protobuf.StringValue description = 5; + google.protobuf.StringValue mobile = 6; + google.protobuf.StringValue email = 7; + google.protobuf.Int32Value request_status_code = 8; + google.protobuf.StringValue request_status_message = 9; + google.protobuf.StringValue authority = 10; + google.protobuf.StringValue fee_type = 11; + google.protobuf.Int64Value fee = 12; + oneof Currency_item + { + pyms_messages.CurrencyEnum currency = 13; + } + google.protobuf.BoolValue payment_status = 14; + google.protobuf.Int32Value verification_status_code = 15; + google.protobuf.StringValue verification_status_message = 16; + google.protobuf.StringValue card_hash = 17; + google.protobuf.StringValue card_pan = 18; + google.protobuf.StringValue ref_id = 19; + google.protobuf.StringValue order_id = 20; + oneof Type_item + { + pyms_messages.TransactionTypeEnum type = 21; + } +} + +message GetAllTransactionByFilterResponse +{ + pyms_messages.MetaData meta_data = 1; + repeated GetAllTransactionByFilterResponseModel models = 2; +} + +message GetAllTransactionByFilterResponseModel +{ + int64 id = 1; + string merchant_id = 2; + int64 amount = 3; + string callback_url = 4; + string description = 5; + google.protobuf.StringValue mobile = 6; + google.protobuf.StringValue email = 7; + google.protobuf.Int32Value request_status_code = 8; + google.protobuf.StringValue request_status_message = 9; + google.protobuf.StringValue authority = 10; + google.protobuf.StringValue fee_type = 11; + google.protobuf.Int64Value fee = 12; + oneof Currency_item + { + pyms_messages.CurrencyEnum currency = 13; + } + bool payment_status = 14; + google.protobuf.Int32Value verification_status_code = 15; + google.protobuf.StringValue verification_status_message = 16; + google.protobuf.StringValue card_hash = 17; + google.protobuf.StringValue card_pan = 18; + google.protobuf.StringValue ref_id = 19; + google.protobuf.StringValue order_id = 20; + oneof Type_item + { + pyms_messages.TransactionTypeEnum type = 21; + } +} + +message PaymentRequestRequest +{ + google.protobuf.StringValue merchant_id = 1; + int64 amount = 2; + string callback_url = 3; + google.protobuf.StringValue description = 4; + google.protobuf.StringValue mobile = 5; + google.protobuf.StringValue email = 6; + oneof Currency_item + { + pyms_messages.CurrencyEnum currency = 7; + } + oneof Type_item + { + pyms_messages.TransactionTypeEnum type = 8; + } + google.protobuf.StringValue order_id = 9; +} + +message PaymentRequestResponse +{ + string payment_g_w_url = 1; +} + +message PaymentVerificationRequest +{ + string authority = 1; + string status = 2; +} + +message PaymentVerificationResponse +{ + int64 id = 1; + bool payment_status = 2; + string message = 3; + google.protobuf.StringValue ref_id = 4; + google.protobuf.StringValue order_id = 5; + google.protobuf.Int32Value verification_status_code = 6; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/sitepage.proto b/src/CMSMicroservice.Protobuf/Protos/sitepage.proto new file mode 100644 index 0000000..4ba416c --- /dev/null +++ b/src/CMSMicroservice.Protobuf/Protos/sitepage.proto @@ -0,0 +1,195 @@ +syntax = "proto3"; + +package sitepage; + +import "public_messages.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; +import "google/api/annotations.proto"; + +option csharp_namespace = "CMSMicroservice.Protobuf.Protos.SitePage"; + +service SitePageContract +{ + rpc GetSitePage(GetSitePageRequest) returns (GetSitePageResponse){ + option (google.api.http) = { get: "/GetSitePage" }; + }; + rpc GetSitePageByKey(GetSitePageByKeyRequest) returns (GetSitePageResponse){ + option (google.api.http) = { get: "/GetSitePageByKey" }; + }; + rpc CreateSitePage(CreateSitePageRequest) returns (CreateSitePageResponse){ + option (google.api.http) = { post: "/CreateSitePage" body: "*" }; + }; + rpc UpdateSitePage(UpdateSitePageRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { put: "/UpdateSitePage" body: "*" }; + }; + rpc DeleteSitePage(DeleteSitePageRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { delete: "/DeleteSitePage" body: "*" }; + }; + rpc GetAllSitePages(GetAllSitePagesRequest) returns (GetAllSitePagesResponse){ + option (google.api.http) = { get: "/GetAllSitePages" }; + }; + rpc CreateSitePageSection(CreateSitePageSectionRequest) returns (CreateSitePageSectionResponse){ + option (google.api.http) = { post: "/CreateSitePageSection" body: "*" }; + }; + rpc UpdateSitePageSection(UpdateSitePageSectionRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { put: "/UpdateSitePageSection" body: "*" }; + }; + rpc DeleteSitePageSection(DeleteSitePageSectionRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { delete: "/DeleteSitePageSection" body: "*" }; + }; + rpc ReorderSitePageSections(ReorderSitePageSectionsRequest) returns (google.protobuf.Empty){ + option (google.api.http) = { put: "/ReorderSitePageSections" body: "*" }; + }; +} + +// ── Get Site Page ── +message GetSitePageRequest +{ + int64 id = 1; +} +message GetSitePageByKeyRequest +{ + string page_key = 1; +} +message GetSitePageResponse +{ + int64 id = 1; + string page_key = 2; + string title = 3; + google.protobuf.StringValue meta_description = 4; + google.protobuf.StringValue hero_title = 5; + google.protobuf.StringValue hero_subtitle = 6; + google.protobuf.StringValue hero_image_path = 7; + bool is_active = 8; + repeated SitePageSectionItem sections = 9; +} +message SitePageSectionItem +{ + int64 id = 1; + string section_key = 2; + string title = 3; + google.protobuf.StringValue subtitle = 4; + google.protobuf.StringValue html_content = 5; + google.protobuf.StringValue icon_name = 6; + google.protobuf.StringValue image_path = 7; + google.protobuf.StringValue image_thumbnail_path = 8; + int32 sort_order = 9; + bool is_active = 10; + google.protobuf.StringValue extra_data = 11; +} + +// ── Update Site Page ── +message UpdateSitePageRequest +{ + int64 id = 1; + string title = 2; + google.protobuf.StringValue meta_description = 3; + google.protobuf.StringValue hero_title = 4; + google.protobuf.StringValue hero_subtitle = 5; + google.protobuf.StringValue hero_image_path = 6; + bool is_active = 7; + SitePageImageFileModel image_file = 8; +} + +// ── Get All ── +message GetAllSitePagesRequest {} +message GetAllSitePagesResponse +{ + repeated SitePageSummary pages = 1; +} +message SitePageSummary +{ + int64 id = 1; + string page_key = 2; + string title = 3; + bool is_active = 4; + int32 section_count = 5; + google.protobuf.Timestamp last_modified = 6; +} + +// ── Create Section ── +message CreateSitePageSectionRequest +{ + int64 site_page_id = 1; + string section_key = 2; + string title = 3; + google.protobuf.StringValue subtitle = 4; + google.protobuf.StringValue html_content = 5; + google.protobuf.StringValue icon_name = 6; + google.protobuf.StringValue image_path = 7; + google.protobuf.StringValue image_thumbnail_path = 8; + int32 sort_order = 9; + google.protobuf.StringValue extra_data = 10; + SitePageImageFileModel image_file = 11; +} +message CreateSitePageSectionResponse +{ + int64 id = 1; +} + +// ── Update Section ── +message UpdateSitePageSectionRequest +{ + int64 id = 1; + string section_key = 2; + string title = 3; + google.protobuf.StringValue subtitle = 4; + google.protobuf.StringValue html_content = 5; + google.protobuf.StringValue icon_name = 6; + google.protobuf.StringValue image_path = 7; + google.protobuf.StringValue image_thumbnail_path = 8; + int32 sort_order = 9; + bool is_active = 10; + google.protobuf.StringValue extra_data = 11; + SitePageImageFileModel image_file = 12; +} + +// ── Delete Section ── +message DeleteSitePageSectionRequest +{ + int64 id = 1; +} + +// ── Reorder Sections ── +message ReorderSitePageSectionsRequest +{ + repeated SectionSortItem items = 1; +} +message SectionSortItem +{ + int64 id = 1; + int32 sort_order = 2; +} + +// ── Create Site Page ── +message CreateSitePageRequest +{ + string page_key = 1; + string title = 2; + google.protobuf.StringValue meta_description = 3; + google.protobuf.StringValue hero_title = 4; + google.protobuf.StringValue hero_subtitle = 5; + bool is_active = 6; + SitePageImageFileModel image_file = 7; +} +message CreateSitePageResponse +{ + int64 id = 1; +} + +// ── Delete Site Page ── +message DeleteSitePageRequest +{ + int64 id = 1; +} + +// ── File upload model for binary image uploads from BackOffice ── +message SitePageImageFileModel +{ + bytes file = 1; + string mime = 2; + string file_name = 3; +} diff --git a/src/CMSMicroservice.Protobuf/Protos/userorder.proto b/src/CMSMicroservice.Protobuf/Protos/userorder.proto index f170dd4..4ca6ce0 100644 --- a/src/CMSMicroservice.Protobuf/Protos/userorder.proto +++ b/src/CMSMicroservice.Protobuf/Protos/userorder.proto @@ -269,6 +269,9 @@ message GetAllUserOrderByFilterFilter { messages.DeliveryStatus delivery_status = 10; } + // فیلتر بازه تاریخ + google.protobuf.Timestamp from_date = 11; + google.protobuf.Timestamp to_date = 12; } message GetAllUserOrderByFilterResponse { diff --git a/src/CMSMicroservice.WebApi/Common/Behaviours/LoggingBehaviour.cs b/src/CMSMicroservice.WebApi/Common/Behaviours/LoggingBehaviour.cs index 8481daa..d9b9f42 100644 --- a/src/CMSMicroservice.WebApi/Common/Behaviours/LoggingBehaviour.cs +++ b/src/CMSMicroservice.WebApi/Common/Behaviours/LoggingBehaviour.cs @@ -1,13 +1,20 @@ +using Google.Protobuf; using Grpc.Core.Interceptors; using Microsoft.Extensions.Logging; using CMSMicroservice.Application.Common.Interfaces; +using System.Text.RegularExpressions; namespace CMSMicroservice.WebApi.Common.Behaviours; -public class LoggingBehaviour : Interceptor +public partial class LoggingBehaviour : Interceptor { private readonly ILogger _logger; private readonly ICurrentUserService _currentUserService; + + // فیلدهایی که نباید لاگ شوند (بایت‌های تصویر / فایل) + [GeneratedRegex(@"""(File|ImageFile|image_file|file)"":\s*\{[^}]*\}", RegexOptions.Singleline)] + private static partial Regex BinaryFieldPattern(); + public LoggingBehaviour(ILogger logger, ICurrentUserService currentUserService) { _logger = logger; @@ -21,8 +28,11 @@ public class LoggingBehaviour : Interceptor { var requestName = typeof(TRequest).Name; var userId = _currentUserService.UserId ?? string.Empty; - _logger.LogInformation("gRPC Starting receiving call. Type/Method: {Type} / {Method} Request: {Name} {@UserId} {@Request}", - MethodType.Unary, context.Method , requestName, userId, request); + + // لاگ بدون بایت‌های فایل + var safeLog = SanitizeForLog(request); + _logger.LogInformation("gRPC Starting receiving call. Type/Method: {Type} / {Method} Request: {Name} {UserId} {Request}", + MethodType.Unary, context.Method, requestName, userId, safeLog); try { @@ -30,9 +40,26 @@ public class LoggingBehaviour : Interceptor } catch (Exception ex) { - _logger.LogError(ex, "gRPC Request: Unhandled Exception for Request {Name} {@Request}", requestName, request); - + _logger.LogError(ex, "gRPC Request: Unhandled Exception for Request {Name} {Request}", requestName, safeLog); throw; } } + + /// + /// حذف بایت‌های فایل از لاگ — جایگزینی با [BINARY DATA] + /// + private static string SanitizeForLog(T request) + { + if (request is IMessage protoMessage) + { + var json = JsonFormatter.Default.Format(protoMessage); + // حذف محتوای فیلدهای باینری + json = BinaryFieldPattern().Replace(json, "\"$1\": \"[BINARY DATA]\""); + // اگر هنوز رشته‌های base64 طولانی هست، خلاصه کن + if (json.Length > 2000) + return json[..2000] + "... [TRUNCATED]"; + return json; + } + return request?.ToString() ?? ""; + } } diff --git a/src/CMSMicroservice.WebApi/Common/Behaviours/PerformanceBehaviour.cs b/src/CMSMicroservice.WebApi/Common/Behaviours/PerformanceBehaviour.cs index 0a1f266..488a7c3 100644 --- a/src/CMSMicroservice.WebApi/Common/Behaviours/PerformanceBehaviour.cs +++ b/src/CMSMicroservice.WebApi/Common/Behaviours/PerformanceBehaviour.cs @@ -1,15 +1,21 @@ +using Google.Protobuf; using Grpc.Core.Interceptors; using Microsoft.Extensions.Logging; using System.Diagnostics; +using System.Text.RegularExpressions; using CMSMicroservice.Application.Common.Interfaces; namespace CMSMicroservice.WebApi.Common.Behaviours; -public class PerformanceBehaviour : Interceptor +public partial class PerformanceBehaviour : Interceptor { private readonly Stopwatch _timer; private readonly ILogger _logger; private readonly ICurrentUserService _currentUserService; + + [GeneratedRegex(@"""(File|ImageFile|image_file|file)"":\s*\{[^}]*\}", RegexOptions.Singleline)] + private static partial Regex BinaryFieldPattern(); + public PerformanceBehaviour(ILogger logger, ICurrentUserService currentUserService) { _timer = new Stopwatch(); @@ -34,11 +40,25 @@ public class PerformanceBehaviour : Interceptor { var requestName = typeof(TRequest).Name; var userId = _currentUserService.UserId ?? string.Empty; + var safeLog = SanitizeForLog(request); - _logger.LogWarning("gRPC Long Running Request: {Name} ({ElapsedMilliseconds} milliseconds) {@UserId} {@Request}", - requestName, elapsedMilliseconds, userId, request); + _logger.LogWarning("gRPC Long Running Request: {Name} ({ElapsedMilliseconds} milliseconds) {UserId} {Request}", + requestName, elapsedMilliseconds, userId, safeLog); } return response; } + + private static string SanitizeForLog(T request) + { + if (request is IMessage protoMessage) + { + var json = JsonFormatter.Default.Format(protoMessage); + json = BinaryFieldPattern().Replace(json, "\"$1\": \"[BINARY DATA]\""); + if (json.Length > 2000) + return json[..2000] + "... [TRUNCATED]"; + return json; + } + return request?.ToString() ?? ""; + } } diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/BlogCategoryProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/BlogCategoryProfile.cs new file mode 100644 index 0000000..a1076c9 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Common/Mappings/BlogCategoryProfile.cs @@ -0,0 +1,14 @@ +using Mapster; +using ProtoBlogCategory = CMSMicroservice.Protobuf.Protos.BlogCategory; + +namespace CMSMicroservice.WebApi.Common.Mappings; + +public class BlogCategoryProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + // CreateBlogCategory: long → CreateBlogCategoryResponse + config.NewConfig() + .Map(dest => dest.Id, src => src); + } +} diff --git a/src/CMSMicroservice.WebApi/Common/Mappings/BlogPostProfile.cs b/src/CMSMicroservice.WebApi/Common/Mappings/BlogPostProfile.cs new file mode 100644 index 0000000..8afc3f0 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Common/Mappings/BlogPostProfile.cs @@ -0,0 +1,26 @@ +using CMSMicroservice.Application.BlogPostCQ.Commands.PublishBlogPost; +using CMSMicroservice.Application.BlogPostCQ.Commands.ArchiveBlogPost; +using Google.Protobuf.WellKnownTypes; +using Mapster; +using ProtoBlogPost = CMSMicroservice.Protobuf.Protos.BlogPost; + +namespace CMSMicroservice.WebApi.Common.Mappings; + +public class BlogPostProfile : IRegister +{ + void IRegister.Register(TypeAdapterConfig config) + { + // PublishBlogPost: Command result → Proto response + config.NewConfig() + .Map(dest => dest.Success, src => src.Success) + .Map(dest => dest.Message, src => src.Message) + .Map(dest => dest.PublishedAt, src => src.PublishedAt.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(src.PublishedAt.Value, DateTimeKind.Utc)) + : null); + + // ArchiveBlogPost: Command result → Proto response + config.NewConfig() + .Map(dest => dest.Success, src => src.Success) + .Map(dest => dest.Message, src => src.Message); + } +} diff --git a/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs b/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs new file mode 100644 index 0000000..0a99e20 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Controllers/PaymentCallbackController.cs @@ -0,0 +1,132 @@ +using CMSMicroservice.Application.Common.Interfaces; +using CMSMicroservice.Application.DiscountShopCQ.Commands.CompleteOrderPayment; +using MediatR; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.WebApi.Controllers; + +/// +/// Callback endpoint for payment gateways (ZarinPal, etc.) +/// درگاه پرداخت بعد از پرداخت (یا لغو) کاربر را به اینجا redirect می‌کند +/// +[ApiController] +[AllowAnonymous] // کاربر از درگاه بانک برمی‌گردد — JWT ندارد +[ApiExplorerSettings(GroupName = "cms")] +public class PaymentCallbackController : ControllerBase +{ + private readonly ISender _sender; + private readonly IPaymentGatewayService _paymentGateway; + private readonly IApplicationDbContext _context; + private readonly IConfiguration _configuration; + private readonly ILogger _logger; + + public PaymentCallbackController( + ISender sender, + IPaymentGatewayService paymentGateway, + IApplicationDbContext context, + IConfiguration configuration, + ILogger logger) + { + _sender = sender; + _paymentGateway = paymentGateway; + _context = context; + _configuration = configuration; + _logger = logger; + } + + /// + /// Callback برای پرداخت سفارش فروشگاه تخفیفی + /// زرین‌پال کاربر را با Authority و Status به این endpoint برمی‌گرداند + /// + [HttpGet("/api/payment/discount-order/callback")] + public async Task DiscountOrderCallback( + [FromQuery] long orderId, + [FromQuery(Name = "Authority")] string? authority, + [FromQuery(Name = "Status")] string? status, + CancellationToken cancellationToken) + { + var frontOfficeBaseUrl = _configuration["FrontOfficeBaseUrl"] ?? "https://localhost:5268"; + + _logger.LogInformation( + "Payment callback received: OrderId={OrderId}, Authority={Authority}, Status={Status}", + orderId, authority, status); + + try + { + // پیدا کردن سفارش و تراکنش + var order = await _context.DiscountOrders + .Include(o => o.OrderDetails) + .FirstOrDefaultAsync(o => o.Id == orderId, cancellationToken); + + if (order == null) + { + _logger.LogError("Payment callback: Order #{OrderId} not found", orderId); + return Redirect($"{frontOfficeBaseUrl}/discount-store/orders?error=order-not-found"); + } + + var transaction = order.TransactionId.HasValue + ? await _context.Transactions.FirstOrDefaultAsync( + t => t.Id == order.TransactionId.Value, cancellationToken) + : null; + + // تأیید پرداخت از درگاه + bool paymentSuccess = false; + string? refId = null; + + if (string.Equals(status, "OK", StringComparison.OrdinalIgnoreCase) + && !string.IsNullOrEmpty(authority)) + { + // Verify با مبلغ از دیتابیس (تومان) + var verifyResult = await _paymentGateway.VerifyPaymentAsync( + authority, + status!, + order.GatewayAmountPaid, // مبلغ به تومان + cancellationToken); + + paymentSuccess = verifyResult.IsSuccess; + refId = verifyResult.TrackingCode ?? verifyResult.RefId; + + _logger.LogInformation( + "Payment verification for Order #{OrderId}: Success={Success}, RefId={RefId}, Message={Message}", + orderId, paymentSuccess, refId, verifyResult.Message); + } + else + { + _logger.LogWarning("Payment cancelled by user for Order #{OrderId}", orderId); + } + + // تکمیل سفارش از طریق CQRS + var completeResult = await _sender.Send(new CompleteOrderPaymentCommand + { + OrderId = orderId, + TransactionId = transaction?.Id ?? 0, + PaymentSuccess = paymentSuccess, + RefId = refId + }, cancellationToken); + + // Redirect به FrontOffice + if (paymentSuccess && completeResult.Success) + { + _logger.LogInformation("Payment completed successfully for Order #{OrderId}", orderId); + return Redirect( + $"{frontOfficeBaseUrl}/discount-store/order/{orderId}?payment=success"); + } + else + { + _logger.LogWarning("Payment failed for Order #{OrderId}", orderId); + return Redirect( + $"{frontOfficeBaseUrl}/discount-store/order/{orderId}?payment=failed"); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "Payment callback error for Order #{OrderId}", orderId); + return Redirect( + $"{frontOfficeBaseUrl}/discount-store/order/{orderId}?payment=error"); + } + } +} diff --git a/src/CMSMicroservice.WebApi/Controllers/UploadsController.cs b/src/CMSMicroservice.WebApi/Controllers/UploadsController.cs new file mode 100644 index 0000000..4fa8fc3 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Controllers/UploadsController.cs @@ -0,0 +1,150 @@ +using System.IO; +using System.Net.Http; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; + +namespace CMSMicroservice.WebApi.Controllers; + +/// +/// سرویس عمومی سرو تصاویر — فایل‌ها مستقیماً از پوشه Uploads سرو می‌شوند. +/// اگر فایل محلی وجود نداشت، از FMS قدیمی (dl.afrino.co) دانلود و کش می‌شود. +/// +[ApiController] +[AllowAnonymous] +[ApiExplorerSettings(GroupName = "cms")] +public class UploadsController : ControllerBase +{ + private readonly string _uploadRoot; + private readonly string _fmsBaseUrl; + private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + private readonly FileExtensionContentTypeProvider _contentTypeProvider = new(); + + // حداکثر طول مسیر مجاز (جلوگیری از path traversal) + private const int MaxPathLength = 500; + + public UploadsController( + IConfiguration configuration, + IHttpClientFactory httpClientFactory, + ILogger logger) + { + _httpClientFactory = httpClientFactory; + _logger = logger; + + _uploadRoot = configuration["FileStorage:UploadPath"] + ?? Path.Combine(AppContext.BaseDirectory, "Uploads"); + + _fmsBaseUrl = configuration["FMS:Address"]?.TrimEnd('/') ?? string.Empty; + } + + /// + /// سرو عمومی فایل از پوشه Uploads. + /// اگر فایل محلی وجود نداشت و FMS تنظیم شده باشد، از FMS دانلود و کش می‌شود. + /// + /// مسیر نسبی فایل (مثلاً blog/image.jpg) + [HttpGet("uploads/{**path}")] + [ResponseCache(Duration = 86400, Location = ResponseCacheLocation.Any)] // کش مرورگر ۲۴ ساعت + public async Task GetFile(string path) + { + // ── اعتبارسنجی مسیر ── + if (string.IsNullOrWhiteSpace(path) || path.Length > MaxPathLength) + return BadRequest("مسیر نامعتبر"); + + // جلوگیری از path traversal + if (path.Contains("..") || path.Contains('\\')) + return BadRequest("مسیر نامعتبر"); + + var sanitizedPath = path.TrimStart('/'); + var fullPath = Path.GetFullPath(Path.Combine(_uploadRoot, sanitizedPath)); + + // اطمینان از اینکه مسیر درون _uploadRoot باقی می‌ماند + if (!fullPath.StartsWith(Path.GetFullPath(_uploadRoot), StringComparison.OrdinalIgnoreCase)) + return BadRequest("مسیر نامعتبر"); + + // ── سرو فایل محلی ── + if (System.IO.File.Exists(fullPath)) + return ServeFile(fullPath); + + // ── Fallback: دانلود از FMS قدیمی ── + if (string.IsNullOrWhiteSpace(_fmsBaseUrl)) + { + _logger.LogWarning("File not found locally and no FMS configured: {Path}", sanitizedPath); + return NotFound(); + } + + var downloaded = await TryDownloadFromFmsAsync(sanitizedPath, fullPath); + if (downloaded) + { + _logger.LogInformation("Downloaded and cached from FMS: {Path}", sanitizedPath); + return ServeFile(fullPath); + } + + return NotFound(); + } + + // ──────────────────────────────────────────────────── + // سرو فایل با Content-Type مناسب + // ──────────────────────────────────────────────────── + private IActionResult ServeFile(string fullPath) + { + if (!_contentTypeProvider.TryGetContentType(fullPath, out var contentType)) + contentType = "application/octet-stream"; + + var stream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read); + return File(stream, contentType, enableRangeProcessing: true); + } + + // ──────────────────────────────────────────────────── + // دانلود از FMS قدیمی و ذخیره محلی + // ──────────────────────────────────────────────────── + private async Task TryDownloadFromFmsAsync(string relativePath, string localPath) + { + try + { + var fmsUrl = $"{_fmsBaseUrl}/{relativePath}"; + _logger.LogInformation("Attempting FMS download: {Url}", fmsUrl); + + using var client = _httpClientFactory.CreateClient("FMS"); + using var response = await client.GetAsync(fmsUrl, HttpCompletionOption.ResponseHeadersRead); + + if (!response.IsSuccessStatusCode) + { + _logger.LogWarning("FMS returned {Status} for {Url}", response.StatusCode, fmsUrl); + return false; + } + + // بررسی Content-Type — فقط فایل‌های تصویری/مجاز + var mediaType = response.Content.Headers.ContentType?.MediaType ?? string.Empty; + if (!IsAllowedMediaType(mediaType)) + { + _logger.LogWarning("FMS returned disallowed content type {Type} for {Url}", mediaType, fmsUrl); + return false; + } + + // ذخیره روی دیسک + var directory = Path.GetDirectoryName(localPath)!; + Directory.CreateDirectory(directory); + + await using var fileStream = new FileStream(localPath, FileMode.Create, FileAccess.Write, FileShare.None); + await response.Content.CopyToAsync(fileStream); + + _logger.LogInformation("Cached FMS file locally: {Path} ({Size} bytes)", relativePath, fileStream.Length); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to download from FMS: {Path}", relativePath); + return false; + } + } + + private static bool IsAllowedMediaType(string mediaType) + { + return mediaType.StartsWith("image/", StringComparison.OrdinalIgnoreCase) + || mediaType is "application/pdf" + or "application/octet-stream"; + } +} diff --git a/src/CMSMicroservice.WebApi/Interceptors/ImagePathResolverInterceptor.cs b/src/CMSMicroservice.WebApi/Interceptors/ImagePathResolverInterceptor.cs new file mode 100644 index 0000000..30322b5 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Interceptors/ImagePathResolverInterceptor.cs @@ -0,0 +1,164 @@ +using CMSMicroservice.Application.Common.FileManager; +using Google.Protobuf; +using Google.Protobuf.Reflection; +using Grpc.Core; +using Grpc.Core.Interceptors; +using Microsoft.Extensions.Logging; +using System.Collections; +using System.Collections.Generic; + +namespace CMSMicroservice.WebApi.Interceptors; + +/// +/// gRPC Interceptor — بعد از اجرای هر سرویس، فیلدهای تصویری response را +/// از مسیر نسبی دیسک به base64 data-URI تبدیل می‌کند +/// +public class ImagePathResolverInterceptor : Interceptor +{ + private readonly IFileManager _fileManager; + private readonly ILogger _logger; + + // نام فیلدهایی که مسیر تصویر هستند + private static readonly HashSet ImageFieldNames = new(StringComparer.OrdinalIgnoreCase) + { + "image_path", + "thumbnail_path", + "image_thumbnail_path", + "featured_image_path", + "featured_image_thumbnail_path", + "hero_image_path", + "product_thumbnail_path", + "avatar_path", + "avatar_url", + "avatar" + }; + + public ImagePathResolverInterceptor(IFileManager fileManager, ILogger logger) + { + _fileManager = fileManager; + _logger = logger; + } + + // ── Unary call (اکثر gRPC‌ها) ── + public override async Task UnaryServerHandler( + TRequest request, + ServerCallContext context, + UnaryServerMethod continuation) + { + var response = await continuation(request, context); + + if (response is IMessage message) + { + ResolveImagePaths(message); + } + + return response; + } + + // ── Server streaming ── + public override async Task ServerStreamingServerHandler( + TRequest request, + IServerStreamWriter responseStream, + ServerCallContext context, + ServerStreamingServerMethod continuation) + { + var wrappedStream = new ImageResolvingStreamWriter(responseStream, this); + await continuation(request, wrappedStream, context); + } + + /// + /// بازگشتی: تمام فیلدهای string با نام تصویری را resolve می‌کند + /// شامل فیلدهای تکراری (repeated) و زیر-پیام‌ها (sub-messages) + /// + internal void ResolveImagePaths(IMessage message) + { + var descriptor = message.Descriptor; + + foreach (var field in descriptor.Fields.InFieldNumberOrder()) + { + try + { + if (field.FieldType == FieldType.String && ImageFieldNames.Contains(field.Name)) + { + // فیلد string ساده + var accessor = field.Accessor; + var value = accessor.GetValue(message) as string; + if (!string.IsNullOrEmpty(value)) + { + var resolved = _fileManager.ResolveImageUrl(value); + accessor.SetValue(message, resolved); + } + } + else if (field.FieldType == FieldType.Message) + { + if (field.IsRepeated) + { + // repeated sub-message + var list = field.Accessor.GetValue(message) as System.Collections.IList; + if (list != null) + { + foreach (var item in list) + { + if (item is IMessage subMsg) + ResolveImagePaths(subMsg); + } + } + } + else + { + // فیلد oneof یا فیلد optional message + var subMessage = field.Accessor.GetValue(message) as IMessage; + if (subMessage != null) + { + // Google.Protobuf.WellKnownTypes.StringValue wrapper + if (subMessage is Google.Protobuf.WellKnownTypes.StringValue sv + && ImageFieldNames.Contains(field.Name)) + { + if (!string.IsNullOrEmpty(sv.Value)) + { + sv.Value = _fileManager.ResolveImageUrl(sv.Value); + } + } + else + { + ResolveImagePaths(subMessage); + } + } + } + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Error resolving image path for field {Field}", field.Name); + } + } + } + + /// + /// Wrapper برای Server Streaming — هر پیام قبل از ارسال resolve می‌شود + /// + private class ImageResolvingStreamWriter : IServerStreamWriter where T : class + { + private readonly IServerStreamWriter _inner; + private readonly ImagePathResolverInterceptor _interceptor; + + public ImageResolvingStreamWriter(IServerStreamWriter inner, ImagePathResolverInterceptor interceptor) + { + _inner = inner; + _interceptor = interceptor; + } + + public WriteOptions? WriteOptions + { + get => _inner.WriteOptions; + set => _inner.WriteOptions = value; + } + + public Task WriteAsync(T message) + { + if (message is IMessage msg) + _interceptor.ResolveImagePaths(msg); + return _inner.WriteAsync(message); + } + } +} diff --git a/src/CMSMicroservice.WebApi/Program.cs b/src/CMSMicroservice.WebApi/Program.cs index 65f51e0..c674c6f 100644 --- a/src/CMSMicroservice.WebApi/Program.cs +++ b/src/CMSMicroservice.WebApi/Program.cs @@ -65,6 +65,7 @@ builder.Services.AddGrpc(options => options.Interceptors.Add(); options.Interceptors.Add(); options.Interceptors.Add(); + options.Interceptors.Add(); //options.Interceptors.Add(); options.EnableDetailedErrors = true; options.MaxReceiveMessageSize = 1000 * 1024 * 1024; // 1 GB @@ -92,6 +93,13 @@ builder.Services.AddHealthChecks() // Add Controllers for REST APIs builder.Services.AddControllers(); +// HttpClient for FMS fallback image download +builder.Services.AddHttpClient("FMS", client => +{ + client.Timeout = TimeSpan.FromSeconds(30); + client.DefaultRequestHeaders.Add("User-Agent", "FourSat-CMS/1.0"); +}); + #region Configure Cors builder.Services.AddCors(options => diff --git a/src/CMSMicroservice.WebApi/Services/AppVersionService.cs b/src/CMSMicroservice.WebApi/Services/AppVersionService.cs index f1b1fcf..53fe15d 100644 --- a/src/CMSMicroservice.WebApi/Services/AppVersionService.cs +++ b/src/CMSMicroservice.WebApi/Services/AppVersionService.cs @@ -16,7 +16,6 @@ public class AppVersionService : AppVersionContract.AppVersionContractBase _dispatchRequestToCQRS = dispatchRequestToCQRS; } - [RequiresPermission(PermissionNames.SettingsView)] public override async Task GetAppVersion(GetAppVersionRequest request, ServerCallContext context) { return await _dispatchRequestToCQRS.Handle(request, context); diff --git a/src/CMSMicroservice.WebApi/Services/BlogCategoryService.cs b/src/CMSMicroservice.WebApi/Services/BlogCategoryService.cs new file mode 100644 index 0000000..d5f22dc --- /dev/null +++ b/src/CMSMicroservice.WebApi/Services/BlogCategoryService.cs @@ -0,0 +1,116 @@ +using CMSMicroservice.Protobuf.Protos.BlogCategory; +using CMSMicroservice.WebApi.Common.Services; +using CMSMicroservice.Application.BlogCategoryCQ.Commands.CreateBlogCategory; +using CMSMicroservice.Application.BlogCategoryCQ.Commands.UpdateBlogCategory; +using CMSMicroservice.Application.BlogCategoryCQ.Commands.DeleteBlogCategory; +using CMSMicroservice.Application.BlogCategoryCQ.Queries.GetBlogCategory; +using CMSMicroservice.Application.BlogCategoryCQ.Queries.GetAllBlogCategories; +using CMSMicroservice.Application.BlogCategoryCQ.Queries.GetActiveBlogCategories; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using Mapster; +using MediatR; +using AppModels = CMSMicroservice.Application.Common.Models; +using ProtoMetaData = CMSMicroservice.Protobuf.Protos.MetaData; + +namespace CMSMicroservice.WebApi.Services; + +public class BlogCategoryService : BlogCategoryContract.BlogCategoryContractBase +{ + private readonly ISender _sender; + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public BlogCategoryService(ISender sender, IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _sender = sender; + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + + public override async Task CreateBlogCategory(CreateBlogCategoryRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task UpdateBlogCategory(UpdateBlogCategoryRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task DeleteBlogCategory(DeleteBlogCategoryRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task GetBlogCategory(GetBlogCategoryRequest request, ServerCallContext context) + { + var query = new GetBlogCategoryQuery { Id = request.Id }; + var result = await _sender.Send(query, context.CancellationToken); + return MapCategoryToResponse(result); + } + + public override async Task GetAllBlogCategories(GetAllBlogCategoriesRequest request, ServerCallContext context) + { + var query = new GetAllBlogCategoriesQuery + { + PageNumber = request.PaginationState?.PageNumber ?? 1, + PageSize = request.PaginationState?.PageSize ?? 20, + SearchTerm = request.SortBy + }; + + var result = await _sender.Send(query, context.CancellationToken); + var response = new GetAllBlogCategoriesResponse + { + MetaData = result.MetaData.Adapt() + }; + + foreach (var item in result.Models) + response.Models.Add(MapToListItem(item)); + + return response; + } + + public override async Task GetActiveBlogCategories(GetActiveBlogCategoriesRequest request, ServerCallContext context) + { + var query = new GetActiveBlogCategoriesQuery(); + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetActiveBlogCategoriesResponse(); + foreach (var item in result) + response.Categories.Add(MapToListItem(item)); + + return response; + } + + // ── Private Mapping Helpers ── + + private static GetBlogCategoryResponse MapCategoryToResponse(BlogCategoryDto dto) + { + return new GetBlogCategoryResponse + { + Id = dto.Id, + Title = dto.Title ?? string.Empty, + Slug = dto.Slug ?? string.Empty, + Description = dto.Description, + IconName = dto.IconName, + SortOrder = dto.SortOrder, + IsActive = dto.IsActive, + PostCount = dto.PostCount, + Created = dto.Created != default ? Timestamp.FromDateTime(DateTime.SpecifyKind(dto.Created, DateTimeKind.Utc)) : null + }; + } + + private static BlogCategoryListItem MapToListItem(BlogCategoryDto dto) + { + return new BlogCategoryListItem + { + Id = dto.Id, + Title = dto.Title ?? string.Empty, + Slug = dto.Slug ?? string.Empty, + Description = dto.Description, + IconName = dto.IconName, + SortOrder = dto.SortOrder, + IsActive = dto.IsActive, + PostCount = dto.PostCount + }; + } +} diff --git a/src/CMSMicroservice.WebApi/Services/BlogPostImageService.cs b/src/CMSMicroservice.WebApi/Services/BlogPostImageService.cs new file mode 100644 index 0000000..1c22019 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Services/BlogPostImageService.cs @@ -0,0 +1,72 @@ +using System.Linq; +using CMSMicroservice.Protobuf.Protos.BlogPostImage; +using CMSMicroservice.WebApi.Common.Services; +using CMSMicroservice.Application.BlogPostImageCQ.Commands.AddBlogPostImage; +using CMSMicroservice.Application.BlogPostImageCQ.Commands.DeleteBlogPostImage; +using CMSMicroservice.Application.BlogPostImageCQ.Commands.ReorderBlogPostImages; +using CMSMicroservice.Application.BlogPostImageCQ.Queries.GetBlogPostImages; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using MediatR; + +namespace CMSMicroservice.WebApi.Services; + +public class BlogPostImageService : BlogPostImageContract.BlogPostImageContractBase +{ + private readonly ISender _sender; + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public BlogPostImageService(ISender sender, IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _sender = sender; + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + + public override async Task AddBlogPostImage(AddBlogPostImageRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task DeleteBlogPostImage(DeleteBlogPostImageRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task GetBlogPostImages(GetBlogPostImagesRequest request, ServerCallContext context) + { + var query = new GetBlogPostImagesQuery { BlogPostId = request.BlogPostId }; + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetBlogPostImagesResponse(); + foreach (var item in result) + { + response.Images.Add(new BlogPostImageItem + { + Id = item.Id, + BlogPostId = item.BlogPostId, + ImagePath = item.ImagePath ?? string.Empty, + ThumbnailPath = item.ThumbnailPath ?? string.Empty, + AltText = item.AltText, + Caption = item.Caption, + SortOrder = item.SortOrder + }); + } + + return response; + } + + public override async Task ReorderBlogPostImages(ReorderBlogPostImagesRequest request, ServerCallContext context) + { + var command = new ReorderBlogPostImagesCommand + { + Items = request.Items.Select(x => new Application.BlogPostImageCQ.Commands.ReorderBlogPostImages.ImageSortItem + { + Id = x.Id, + SortOrder = x.SortOrder + }).ToList() + }; + + await _sender.Send(command, context.CancellationToken); + return new Empty(); + } +} diff --git a/src/CMSMicroservice.WebApi/Services/BlogPostService.cs b/src/CMSMicroservice.WebApi/Services/BlogPostService.cs new file mode 100644 index 0000000..18450a8 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Services/BlogPostService.cs @@ -0,0 +1,231 @@ +using System.Collections.Generic; +using System.Linq; +using CMSMicroservice.Protobuf.Protos.BlogPost; +using CMSMicroservice.WebApi.Common.Services; +using CMSMicroservice.Application.BlogPostCQ.Commands.CreateBlogPost; +using CMSMicroservice.Application.BlogPostCQ.Commands.UpdateBlogPost; +using CMSMicroservice.Application.BlogPostCQ.Commands.DeleteBlogPost; +using CMSMicroservice.Application.BlogPostCQ.Commands.PublishBlogPost; +using CMSMicroservice.Application.BlogPostCQ.Commands.ArchiveBlogPost; +using CMSMicroservice.Application.BlogPostCQ.Commands.IncrementViewCount; +using CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPost; +using CMSMicroservice.Application.BlogPostCQ.Queries.GetBlogPostBySlug; +using CMSMicroservice.Application.BlogPostCQ.Queries.GetAllBlogPosts; +using CMSMicroservice.Application.BlogPostCQ.Queries.GetPublishedBlogPosts; +using CMSMicroservice.Application.BlogPostCQ.Queries.GetFeaturedBlogPosts; +using CMSMicroservice.Domain.Enums; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using Mapster; +using MediatR; +using AppModels = CMSMicroservice.Application.Common.Models; +using ProtoMetaData = CMSMicroservice.Protobuf.Protos.MetaData; + +namespace CMSMicroservice.WebApi.Services; + +public class BlogPostService : BlogPostContract.BlogPostContractBase +{ + private readonly ISender _sender; + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public BlogPostService(ISender sender, IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _sender = sender; + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + + public override async Task CreateBlogPost(CreateBlogPostRequest request, ServerCallContext context) + { + var command = new CreateBlogPostCommand + { + Title = request.Title, + Slug = request.Slug, + Summary = request.Summary, + HtmlContent = request.HtmlContent, + FeaturedImagePath = request.FeaturedImagePath, + FeaturedImageThumbnailPath = request.FeaturedImageThumbnailPath, + CategoryIds = request.CategoryIds.ToList(), + TagIds = request.TagIds.ToList(), + IsFeatured = request.IsFeatured, + SortOrder = request.SortOrder, + ImageFileBytes = request.ImageFile?.File?.ToByteArray(), + ImageFileMime = request.ImageFile?.Mime, + ImageFileName = request.ImageFile?.FileName + }; + + var result = await _sender.Send(command, context.CancellationToken); + return new CreateBlogPostResponse { Id = result }; + } + + public override async Task UpdateBlogPost(UpdateBlogPostRequest request, ServerCallContext context) + { + var command = new UpdateBlogPostCommand + { + Id = request.Id, + Title = request.Title, + Slug = request.Slug, + Summary = request.Summary, + HtmlContent = request.HtmlContent, + FeaturedImagePath = request.FeaturedImagePath, + FeaturedImageThumbnailPath = request.FeaturedImageThumbnailPath, + CategoryIds = request.CategoryIds.ToList(), + TagIds = request.TagIds.ToList(), + IsFeatured = request.IsFeatured, + SortOrder = request.SortOrder, + ImageFileBytes = request.ImageFile?.File?.ToByteArray(), + ImageFileMime = request.ImageFile?.Mime, + ImageFileName = request.ImageFile?.FileName + }; + + await _sender.Send(command, context.CancellationToken); + return new Empty(); + } + + public override async Task DeleteBlogPost(DeleteBlogPostRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task GetBlogPost(GetBlogPostRequest request, ServerCallContext context) + { + var query = new GetBlogPostQuery { Id = request.Id }; + var result = await _sender.Send(query, context.CancellationToken); + return MapBlogPostDtoToResponse(result); + } + + public override async Task GetBlogPostBySlug(GetBlogPostBySlugRequest request, ServerCallContext context) + { + var query = new GetBlogPostBySlugQuery { Slug = request.Slug }; + var result = await _sender.Send(query, context.CancellationToken); + return MapBlogPostDtoToResponse(result); + } + + public override async Task GetAllBlogPosts(GetAllBlogPostsRequest request, ServerCallContext context) + { + var query = new GetAllBlogPostsQuery + { + PageNumber = request.PaginationState?.PageNumber ?? 1, + PageSize = request.PaginationState?.PageSize ?? 10, + SortBy = request.SortBy, + SearchTerm = request.Filter?.SearchTerm, + Status = request.Filter?.Status.HasValue == true ? (BlogPostStatus?)request.Filter.Status.Value : null, + CategoryId = request.Filter?.CategoryId, + IsFeatured = request.Filter?.IsFeatured + }; + + var result = await _sender.Send(query, context.CancellationToken); + return MapAllBlogPostsResponse(result); + } + + public override async Task GetPublishedBlogPosts(GetPublishedBlogPostsRequest request, ServerCallContext context) + { + var query = new GetPublishedBlogPostsQuery + { + PageNumber = request.PaginationState?.PageNumber ?? 1, + PageSize = request.PaginationState?.PageSize ?? 10, + SearchTerm = request.SearchTerm, + CategoryId = request.CategoryId + }; + + var result = await _sender.Send(query, context.CancellationToken); + return MapAllBlogPostsResponse(result); + } + + public override async Task GetFeaturedBlogPosts(GetFeaturedBlogPostsRequest request, ServerCallContext context) + { + var query = new GetFeaturedBlogPostsQuery { Count = request.Count > 0 ? request.Count : 5 }; + var items = await _sender.Send(query, context.CancellationToken); + + var response = new GetAllBlogPostsResponse(); + foreach (var item in items) + response.Models.Add(MapToListItem(item)); + return response; + } + + public override async Task PublishBlogPost(PublishBlogPostRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task ArchiveBlogPost(ArchiveBlogPostRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task IncrementViewCount(IncrementViewCountRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + // ── Private Mapping Helpers ── + + private static GetBlogPostResponse MapBlogPostDtoToResponse(BlogPostDto dto) + { + var response = new GetBlogPostResponse + { + Id = dto.Id, + Title = dto.Title ?? string.Empty, + Slug = dto.Slug ?? string.Empty, + Summary = dto.Summary, + HtmlContent = dto.HtmlContent ?? string.Empty, + FeaturedImagePath = dto.FeaturedImagePath, + FeaturedImageThumbnailPath = dto.FeaturedImageThumbnailPath, + Status = (int)dto.Status, + StatusName = dto.StatusName ?? string.Empty, + ViewCount = dto.ViewCount, + AuthorUserId = dto.AuthorUserId, + IsFeatured = dto.IsFeatured, + SortOrder = dto.SortOrder, + Created = dto.Created != default ? Timestamp.FromDateTime(DateTime.SpecifyKind(dto.Created, DateTimeKind.Utc)) : null, + LastModified = dto.LastModified.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(dto.LastModified.Value, DateTimeKind.Utc)) : null, + PublishedAt = dto.PublishedAt.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(dto.PublishedAt.Value, DateTimeKind.Utc)) : null + }; + + if (dto.Categories != null) + foreach (var c in dto.Categories) + response.Categories.Add(new BlogPostCategoryInfo { Id = c.Id, Title = c.Title ?? string.Empty, Slug = c.Slug ?? string.Empty }); + + if (dto.Tags != null) + foreach (var t in dto.Tags) + response.Tags.Add(new BlogPostTagInfo { Id = t.Id, Title = t.Title ?? string.Empty, Name = t.Name ?? string.Empty }); + + return response; + } + + private static GetAllBlogPostsResponse MapAllBlogPostsResponse(GetAllBlogPostsResponseDto dto) + { + var response = new GetAllBlogPostsResponse + { + MetaData = dto.MetaData.Adapt() + }; + + foreach (var item in dto.Models) + response.Models.Add(MapToListItem(item)); + + return response; + } + + private static BlogPostListItem MapToListItem(BlogPostListItemDto item) + { + var listItem = new BlogPostListItem + { + Id = item.Id, + Title = item.Title ?? string.Empty, + Slug = item.Slug ?? string.Empty, + Summary = item.Summary, + FeaturedImageThumbnailPath = item.FeaturedImageThumbnailPath, + Status = item.Status, + StatusName = item.StatusName ?? string.Empty, + ViewCount = item.ViewCount, + IsFeatured = item.IsFeatured, + Created = item.Created != default ? Timestamp.FromDateTime(DateTime.SpecifyKind(item.Created, DateTimeKind.Utc)) : null, + PublishedAt = item.PublishedAt.HasValue ? Timestamp.FromDateTime(DateTime.SpecifyKind(item.PublishedAt.Value, DateTimeKind.Utc)) : null + }; + + if (item.Categories != null) + foreach (var c in item.Categories) + listItem.Categories.Add(new BlogPostCategoryInfo { Id = c.Id, Title = c.Title ?? string.Empty, Slug = c.Slug ?? string.Empty }); + + return listItem; + } +} diff --git a/src/CMSMicroservice.WebApi/Services/DiscountOrderService.cs b/src/CMSMicroservice.WebApi/Services/DiscountOrderService.cs index d2df7d5..d5850c4 100644 --- a/src/CMSMicroservice.WebApi/Services/DiscountOrderService.cs +++ b/src/CMSMicroservice.WebApi/Services/DiscountOrderService.cs @@ -1,5 +1,6 @@ using CMSMicroservice.Protobuf.Protos.DiscountOrder; using CMSMicroservice.WebApi.Common.Services; +using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Application.DiscountShopCQ.Commands.PlaceOrder; using CMSMicroservice.Application.DiscountShopCQ.Commands.CompleteOrderPayment; using CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateOrderStatus; @@ -7,21 +8,57 @@ using CMSMicroservice.Application.DiscountShopCQ.Queries.GetOrderById; using CMSMicroservice.Application.DiscountShopCQ.Queries.GetUserOrders; using CMSMicroservice.Application.DiscountShopCQ.Queries.GetAllDiscountOrders; using CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountSalesReport; +using Grpc.Core; +using Mapster; +using MediatR; namespace CMSMicroservice.WebApi.Services; public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractBase { private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly ISender _sender; + private readonly ICurrentUserService _currentUserService; - public DiscountOrderService(IDispatchRequestToCQRS dispatchRequestToCQRS) + public DiscountOrderService( + IDispatchRequestToCQRS dispatchRequestToCQRS, + ISender sender, + ICurrentUserService currentUserService) { _dispatchRequestToCQRS = dispatchRequestToCQRS; + _sender = sender; + _currentUserService = currentUserService; + } + + private long GetCurrentUserId() + { + if (long.TryParse(_currentUserService.UserId, out var uid) && uid > 0) + return uid; + throw new RpcException(new Status(StatusCode.Unauthenticated, "کاربر احراز هویت نشده است")); } public override async Task PlaceOrder(PlaceOrderRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new PlaceOrderCommand + { + UserId = GetCurrentUserId(), + UserAddressId = request.UserAddressId, + DiscountBalanceToUse = request.DiscountBalanceToUse + }; + var result = await _sender.Send(command); + + var response = new PlaceOrderResponse + { + Success = result.Success, + Message = result.Message ?? string.Empty, + OrderId = result.OrderId ?? 0, + GatewayAmount = result.GatewayAmountRequired, + }; + + if (!string.IsNullOrEmpty(result.PaymentUrl)) + response.PaymentUrl = result.PaymentUrl; + + return response; } public override async Task CompleteOrderPayment(CompleteOrderPaymentRequest request, ServerCallContext context) @@ -36,12 +73,23 @@ public class DiscountOrderService : DiscountOrderContract.DiscountOrderContractB public override async Task GetOrderById(GetOrderByIdRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var query = new GetOrderByIdQuery + { + OrderId = request.OrderId, + UserId = GetCurrentUserId() + }; + var result = await _sender.Send(query); + return result.Adapt(); } public override async Task GetUserOrders(GetUserOrdersRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var query = new GetUserOrdersQuery + { + UserId = GetCurrentUserId() + }; + var result = await _sender.Send(query); + return result.Adapt(); } public override async Task GetAllDiscountOrders(GetAllDiscountOrdersRequest request, ServerCallContext context) diff --git a/src/CMSMicroservice.WebApi/Services/DiscountProductService.cs b/src/CMSMicroservice.WebApi/Services/DiscountProductService.cs index 6f5c6ae..effc833 100644 --- a/src/CMSMicroservice.WebApi/Services/DiscountProductService.cs +++ b/src/CMSMicroservice.WebApi/Services/DiscountProductService.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using System.Linq; using CMSMicroservice.Protobuf.Protos.DiscountProduct; using CMSMicroservice.WebApi.Common.Services; using CMSMicroservice.Application.DiscountShopCQ.Commands.CreateDiscountProduct; @@ -10,26 +12,75 @@ using CMSMicroservice.Application.DiscountShopCQ.Commands.ReorderDiscountProduct using CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProductById; using CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProducts; using CMSMicroservice.Application.DiscountShopCQ.Queries.GetDiscountProductImages; +using MediatR; +using Mapster; namespace CMSMicroservice.WebApi.Services; public class DiscountProductService : DiscountProductContract.DiscountProductContractBase { private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly ISender _sender; - public DiscountProductService(IDispatchRequestToCQRS dispatchRequestToCQRS) + public DiscountProductService(IDispatchRequestToCQRS dispatchRequestToCQRS, ISender sender) { _dispatchRequestToCQRS = dispatchRequestToCQRS; + _sender = sender; } public override async Task CreateDiscountProduct(CreateDiscountProductRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new CreateDiscountProductCommand + { + Title = request.Title, + ShortInfomation = request.ShortInfomation, + FullInformation = request.FullInformation, + Price = request.Price, + MaxDiscountPercent = request.MaxDiscountPercent, + ImagePath = request.ImagePath, + ThumbnailPath = request.ThumbnailPath, + SortOrder = request.SortOrder, + IsActive = request.IsActive, + CategoryIds = request.CategoryIds?.ToList() ?? new List(), + // Map binary image data + ImageFileBytes = request.ImageFile?.File?.ToByteArray(), + ImageFileMime = request.ImageFile?.Mime, + ImageFileName = request.ImageFile?.FileName, + ThumbnailFileBytes = request.ThumbnailFile?.File?.ToByteArray(), + ThumbnailFileMime = request.ThumbnailFile?.Mime, + ThumbnailFileName = request.ThumbnailFile?.FileName + }; + + var productId = await _sender.Send(command, context.CancellationToken); + return new CreateDiscountProductResponse { ProductId = productId }; } public override async Task UpdateDiscountProduct(UpdateDiscountProductRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new UpdateDiscountProductCommand + { + ProductId = request.ProductId, + Title = request.Title, + ShortInfomation = request.ShortInfomation, + FullInformation = request.FullInformation, + Price = request.Price, + MaxDiscountPercent = request.MaxDiscountPercent, + ImagePath = request.ImagePath, + ThumbnailPath = request.ThumbnailPath, + SortOrder = request.SortOrder, + IsActive = request.IsActive, + CategoryIds = request.CategoryIds?.ToList() ?? new List(), + // Map binary image data + ImageFileBytes = request.ImageFile?.File?.ToByteArray(), + ImageFileMime = request.ImageFile?.Mime, + ImageFileName = request.ImageFile?.FileName, + ThumbnailFileBytes = request.ThumbnailFile?.File?.ToByteArray(), + ThumbnailFileMime = request.ThumbnailFile?.Mime, + ThumbnailFileName = request.ThumbnailFile?.FileName + }; + + await _sender.Send(command, context.CancellationToken); + return new Empty(); } public override async Task DeleteDiscountProduct(DeleteDiscountProductRequest request, ServerCallContext context) @@ -70,6 +121,11 @@ public class DiscountProductService : DiscountProductContract.DiscountProductCon public override async Task GetDiscountProductImages(GetDiscountProductImagesRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var query = request.Adapt(); + var images = await _sender.Send(query, context.CancellationToken); + + var response = new GetDiscountProductImagesResponse(); + response.Images.AddRange(images.Select(i => i.Adapt())); + return response; } } diff --git a/src/CMSMicroservice.WebApi/Services/DiscountShoppingCartService.cs b/src/CMSMicroservice.WebApi/Services/DiscountShoppingCartService.cs index 56e2672..4e472e4 100644 --- a/src/CMSMicroservice.WebApi/Services/DiscountShoppingCartService.cs +++ b/src/CMSMicroservice.WebApi/Services/DiscountShoppingCartService.cs @@ -1,44 +1,111 @@ using CMSMicroservice.Protobuf.Protos.DiscountShoppingCart; using CMSMicroservice.WebApi.Common.Services; +using CMSMicroservice.Application.Common.Interfaces; using CMSMicroservice.Application.DiscountShopCQ.Commands.AddToCart; using CMSMicroservice.Application.DiscountShopCQ.Commands.RemoveFromCart; using CMSMicroservice.Application.DiscountShopCQ.Commands.UpdateCartItemCount; using CMSMicroservice.Application.DiscountShopCQ.Commands.ClearCart; using CMSMicroservice.Application.DiscountShopCQ.Queries.GetUserCart; +using Grpc.Core; +using Mapster; +using MediatR; namespace CMSMicroservice.WebApi.Services; public class DiscountShoppingCartService : DiscountShoppingCartContract.DiscountShoppingCartContractBase { private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + private readonly ISender _sender; + private readonly ICurrentUserService _currentUserService; - public DiscountShoppingCartService(IDispatchRequestToCQRS dispatchRequestToCQRS) + public DiscountShoppingCartService( + IDispatchRequestToCQRS dispatchRequestToCQRS, + ISender sender, + ICurrentUserService currentUserService) { _dispatchRequestToCQRS = dispatchRequestToCQRS; + _sender = sender; + _currentUserService = currentUserService; + } + + private long GetCurrentUserId() + { + if (long.TryParse(_currentUserService.UserId, out var uid) && uid > 0) + return uid; + throw new RpcException(new Status(StatusCode.Unauthenticated, "کاربر احراز هویت نشده است")); } public override async Task AddToCart(AddToCartRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new AddToCartCommand + { + UserId = GetCurrentUserId(), + ProductId = request.ProductId, + Count = request.Count + }; + var result = await _sender.Send(command, context.CancellationToken); + return result.Adapt(); } public override async Task RemoveFromCart(RemoveFromCartRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new RemoveFromCartCommand + { + UserId = GetCurrentUserId(), + ProductId = request.ProductId + }; + var result = await _sender.Send(command, context.CancellationToken); + return result.Adapt(); } public override async Task UpdateCartItemCount(UpdateCartItemCountRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new UpdateCartItemCountCommand + { + UserId = GetCurrentUserId(), + ProductId = request.ProductId, + NewCount = request.NewCount + }; + var result = await _sender.Send(command, context.CancellationToken); + return result.Adapt(); } public override async Task GetUserCart(GetUserCartRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var query = new GetUserCartQuery { UserId = GetCurrentUserId() }; + var cart = await _sender.Send(query, context.CancellationToken); + + var response = new GetUserCartResponse + { + TotalPrice = cart.TotalAmount, + TotalDiscountAmount = cart.MaxDiscountAmount, + FinalPrice = cart.MinPayableAmount + }; + + foreach (var item in cart.Items) + { + response.Items.Add(new CMSMicroservice.Protobuf.Protos.DiscountShoppingCart.CartItemDto + { + ProductId = item.ProductId, + ProductTitle = item.ProductTitle ?? string.Empty, + ProductImagePath = item.ProductImagePath ?? string.Empty, + UnitPrice = item.UnitPrice, + MaxDiscountPercent = item.MaxDiscountPercent, + Count = item.Count, + TotalPrice = item.SubTotal, + DiscountAmount = item.MaxDiscountAmount, + FinalPrice = item.MinPayable, + ProductRemainingCount = item.RemainingStock + }); + } + + return response; } public override async Task ClearCart(ClearCartRequest request, ServerCallContext context) { - return await _dispatchRequestToCQRS.Handle(request, context); + var command = new ClearCartCommand { UserId = GetCurrentUserId() }; + await _sender.Send(command, context.CancellationToken); + return new Empty(); } } diff --git a/src/CMSMicroservice.WebApi/Services/ImageResolverService.cs b/src/CMSMicroservice.WebApi/Services/ImageResolverService.cs new file mode 100644 index 0000000..1025c70 --- /dev/null +++ b/src/CMSMicroservice.WebApi/Services/ImageResolverService.cs @@ -0,0 +1,42 @@ +using CMSMicroservice.Application.Common.FileManager; +using CMSMicroservice.Protobuf.Protos.ImageResolver; +using Grpc.Core; + +namespace CMSMicroservice.WebApi.Services; + +/// +/// سرویس اختصاصی resolve تصاویر — مسیر نسبی را به base64 data-URI تبدیل می‌کند. +/// FrontOffice از طریق این سرویس تمام تصاویر دینامیک را دریافت می‌کند. +/// +public class ImageResolverService : ImageResolverContract.ImageResolverContractBase +{ + private readonly IFileManager _fileManager; + + public ImageResolverService(IFileManager fileManager) + { + _fileManager = fileManager; + } + + public override Task ResolveImages(ResolveImagesRequest request, ServerCallContext context) + { + var response = new ResolveImagesResponse(); + + foreach (var path in request.Paths) + { + var dataUri = string.Empty; + + if (!string.IsNullOrWhiteSpace(path)) + { + dataUri = _fileManager.ResolveImageUrl(path); + } + + response.Images.Add(new ResolvedImage + { + OriginalPath = path ?? string.Empty, + DataUri = dataUri ?? string.Empty + }); + } + + return Task.FromResult(response); + } +} diff --git a/src/CMSMicroservice.WebApi/Services/ProductsService.cs b/src/CMSMicroservice.WebApi/Services/ProductsService.cs index ce1751d..9d79a54 100644 --- a/src/CMSMicroservice.WebApi/Services/ProductsService.cs +++ b/src/CMSMicroservice.WebApi/Services/ProductsService.cs @@ -178,6 +178,7 @@ public class ProductsService : ProductsContract.ProductsContractBase CategoryIds = request.Filter?.CategoryId.HasValue == true ? new List { request.Filter.CategoryId.Value } : new List(), + IsActive = request.Filter?.IsActive, SortBy = request.SortBy ?? string.Empty, PaginationState = request.PaginationState != null ? new AppModels.PaginationState @@ -216,7 +217,8 @@ public class ProductsService : ProductsContract.ProductsContractBase SaleCount = m.SaleCount, ViewCount = m.ViewCount, RemainingCount = m.RemainingCount, - CategoryIds = { m.Categories?.Select(c => c.CategoryId) ?? Enumerable.Empty() } + CategoryIds = { m.Categories?.Select(c => c.CategoryId) ?? Enumerable.Empty() }, + IsActive = m.IsActive }) } }; } diff --git a/src/CMSMicroservice.WebApi/Services/SitePageService.cs b/src/CMSMicroservice.WebApi/Services/SitePageService.cs new file mode 100644 index 0000000..1fc787f --- /dev/null +++ b/src/CMSMicroservice.WebApi/Services/SitePageService.cs @@ -0,0 +1,218 @@ +using System.Linq; +using CMSMicroservice.Protobuf.Protos.SitePage; +using CMSMicroservice.WebApi.Common.Services; +using CMSMicroservice.Application.SitePageCQ.Commands.CreateSitePage; +using CMSMicroservice.Application.SitePageCQ.Commands.DeleteSitePage; +using CMSMicroservice.Application.SitePageCQ.Commands.UpdateSitePage; +using CMSMicroservice.Application.SitePageCQ.Commands.CreateSitePageSection; +using CMSMicroservice.Application.SitePageCQ.Commands.UpdateSitePageSection; +using CMSMicroservice.Application.SitePageCQ.Commands.DeleteSitePageSection; +using CMSMicroservice.Application.SitePageCQ.Commands.ReorderSitePageSections; +using CMSMicroservice.Application.SitePageCQ.Queries.GetSitePage; +using CMSMicroservice.Application.SitePageCQ.Queries.GetSitePageByKey; +using CMSMicroservice.Application.SitePageCQ.Queries.GetAllSitePages; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using MediatR; + +namespace CMSMicroservice.WebApi.Services; + +public class SitePageService : SitePageContract.SitePageContractBase +{ + private readonly ISender _sender; + private readonly IDispatchRequestToCQRS _dispatchRequestToCQRS; + + public SitePageService(ISender sender, IDispatchRequestToCQRS dispatchRequestToCQRS) + { + _sender = sender; + _dispatchRequestToCQRS = dispatchRequestToCQRS; + } + + public override async Task GetSitePage(GetSitePageRequest request, ServerCallContext context) + { + var query = new GetSitePageQuery { Id = request.Id }; + var result = await _sender.Send(query, context.CancellationToken); + return MapToResponse(result); + } + + public override async Task GetSitePageByKey(GetSitePageByKeyRequest request, ServerCallContext context) + { + var query = new GetSitePageByKeyQuery { PageKey = request.PageKey }; + var result = await _sender.Send(query, context.CancellationToken); + return MapToResponse(result); + } + + public override async Task UpdateSitePage(UpdateSitePageRequest request, ServerCallContext context) + { + var command = new UpdateSitePageCommand + { + Id = request.Id, + Title = request.Title, + MetaDescription = request.MetaDescription, + HeroTitle = request.HeroTitle, + HeroSubtitle = request.HeroSubtitle, + HeroImagePath = request.HeroImagePath, + IsActive = request.IsActive, + ImageFileBytes = request.ImageFile?.File?.ToByteArray(), + ImageFileMime = request.ImageFile?.Mime, + ImageFileName = request.ImageFile?.FileName + }; + + await _sender.Send(command, context.CancellationToken); + return new Empty(); + } + + public override async Task CreateSitePage(CreateSitePageRequest request, ServerCallContext context) + { + var command = new CreateSitePageCommand + { + PageKey = request.PageKey, + Title = request.Title, + MetaDescription = request.MetaDescription, + HeroTitle = request.HeroTitle, + HeroSubtitle = request.HeroSubtitle, + IsActive = request.IsActive, + ImageFileBytes = request.ImageFile?.File?.ToByteArray(), + ImageFileMime = request.ImageFile?.Mime, + ImageFileName = request.ImageFile?.FileName + }; + + var id = await _sender.Send(command, context.CancellationToken); + return new CreateSitePageResponse { Id = id }; + } + + public override async Task DeleteSitePage(DeleteSitePageRequest request, ServerCallContext context) + { + var command = new DeleteSitePageCommand { Id = request.Id }; + await _sender.Send(command, context.CancellationToken); + return new Empty(); + } + + public override async Task GetAllSitePages(GetAllSitePagesRequest request, ServerCallContext context) + { + var query = new GetAllSitePagesQuery(); + var result = await _sender.Send(query, context.CancellationToken); + + var response = new GetAllSitePagesResponse(); + foreach (var item in result) + { + response.Pages.Add(new SitePageSummary + { + Id = item.Id, + PageKey = item.PageKey ?? string.Empty, + Title = item.Title ?? string.Empty, + IsActive = item.IsActive, + SectionCount = item.SectionCount, + LastModified = item.LastModified.HasValue + ? Timestamp.FromDateTime(DateTime.SpecifyKind(item.LastModified.Value, DateTimeKind.Utc)) + : null + }); + } + + return response; + } + + public override async Task CreateSitePageSection(CreateSitePageSectionRequest request, ServerCallContext context) + { + var command = new CreateSitePageSectionCommand + { + SitePageId = request.SitePageId, + SectionKey = request.SectionKey, + Title = request.Title, + Subtitle = request.Subtitle, + HtmlContent = request.HtmlContent, + IconName = request.IconName, + ImagePath = request.ImagePath, + IsActive = true, + ExtraData = request.ExtraData, + ImageFileBytes = request.ImageFile?.File?.ToByteArray(), + ImageFileMime = request.ImageFile?.Mime, + ImageFileName = request.ImageFile?.FileName + }; + + var result = await _sender.Send(command, context.CancellationToken); + return new CreateSitePageSectionResponse { Id = result }; + } + + public override async Task UpdateSitePageSection(UpdateSitePageSectionRequest request, ServerCallContext context) + { + var command = new UpdateSitePageSectionCommand + { + Id = request.Id, + SectionKey = request.SectionKey, + Title = request.Title, + Subtitle = request.Subtitle, + HtmlContent = request.HtmlContent, + IconName = request.IconName, + ImagePath = request.ImagePath, + SortOrder = request.SortOrder, + IsActive = request.IsActive, + ExtraData = request.ExtraData, + ImageFileBytes = request.ImageFile?.File?.ToByteArray(), + ImageFileMime = request.ImageFile?.Mime, + ImageFileName = request.ImageFile?.FileName + }; + + await _sender.Send(command, context.CancellationToken); + return new Empty(); + } + + public override async Task DeleteSitePageSection(DeleteSitePageSectionRequest request, ServerCallContext context) + { + return await _dispatchRequestToCQRS.Handle(request, context); + } + + public override async Task ReorderSitePageSections(ReorderSitePageSectionsRequest request, ServerCallContext context) + { + var command = new ReorderSitePageSectionsCommand + { + Items = request.Items.Select(x => new Application.SitePageCQ.Commands.ReorderSitePageSections.SectionSortItem + { + Id = x.Id, + SortOrder = x.SortOrder + }).ToList() + }; + + await _sender.Send(command, context.CancellationToken); + return new Empty(); + } + + // ── Private Mapping Helpers ── + + private static GetSitePageResponse MapToResponse(SitePageDto dto) + { + var response = new GetSitePageResponse + { + Id = dto.Id, + PageKey = dto.PageKey ?? string.Empty, + Title = dto.Title ?? string.Empty, + MetaDescription = dto.MetaDescription, + HeroTitle = dto.HeroTitle, + HeroSubtitle = dto.HeroSubtitle, + HeroImagePath = dto.HeroImagePath, + IsActive = dto.IsActive + }; + + if (dto.Sections != null) + { + foreach (var s in dto.Sections) + { + response.Sections.Add(new SitePageSectionItem + { + Id = s.Id, + SectionKey = s.SectionKey ?? string.Empty, + Title = s.Title ?? string.Empty, + Subtitle = s.Subtitle, + HtmlContent = s.HtmlContent, + IconName = s.IconName, + ImagePath = s.ImagePath, + SortOrder = s.SortOrder, + IsActive = s.IsActive, + ExtraData = s.ExtraData + }); + } + } + + return response; + } +} diff --git a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs index 46c2148..aacf89c 100644 --- a/src/CMSMicroservice.WebApi/Services/UserOrderService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserOrderService.cs @@ -167,14 +167,14 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase { UserId = request.Filter?.UserId ?? 0, // 0 means all users (admin view) PaginationState = request.PaginationState?.Adapt(), - PaymentStatusFilter = request.Filter?.PaymentStatus != null + PaymentStatusFilter = request.Filter?.HasPaymentStatus == true ? (int?)request.Filter.PaymentStatus : null, - DeliveryStatusFilter = request.Filter?.DeliveryStatus != null + DeliveryStatusFilter = request.Filter?.HasDeliveryStatus == true ? (int?)request.Filter.DeliveryStatus : null, - FromDate = request.Filter?.PaymentDate?.ToDateTime(), - ToDate = null + FromDate = request.Filter?.FromDate?.ToDateTime() ?? request.Filter?.PaymentDate?.ToDateTime(), + ToDate = request.Filter?.ToDate?.ToDateTime() }; var result = await _sender.Send(query, context.CancellationToken); @@ -261,7 +261,14 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase if (defaultAddress == null) { - throw new RpcException(new Status(StatusCode.FailedPrecondition, "آدرس پیش‌فرض یافت نشد")); + // Check if user has any address at all + var hasAnyAddress = await _context.UserAddresses + .AnyAsync(a => a.UserId == userId && !a.IsDeleted, context.CancellationToken); + + throw new RpcException(new Status(StatusCode.FailedPrecondition, + hasAnyAddress + ? "لطفاً یک آدرس را به عنوان پیش‌فرض انتخاب کنید." + : "آدرسی ثبت نشده است. لطفاً ابتدا یک آدرس اضافه کنید.")); } // Calculate amounts @@ -591,14 +598,14 @@ public class UserOrderService : UserOrderContract.UserOrderContractBase { UserId = customerUserId, PaginationState = request.PaginationState?.Adapt(), - PaymentStatusFilter = request.Filter?.PaymentStatus != null + PaymentStatusFilter = request.Filter?.HasPaymentStatus == true ? (int?)request.Filter.PaymentStatus : null, - DeliveryStatusFilter = request.Filter?.DeliveryStatus != null + DeliveryStatusFilter = request.Filter?.HasDeliveryStatus == true ? (int?)request.Filter.DeliveryStatus : null, - FromDate = request.Filter?.PaymentDate?.ToDateTime(), - ToDate = null + FromDate = request.Filter?.FromDate?.ToDateTime() ?? request.Filter?.PaymentDate?.ToDateTime(), + ToDate = request.Filter?.ToDate?.ToDateTime() }; var result = await _sender.Send(query, context.CancellationToken); diff --git a/src/CMSMicroservice.WebApi/Services/UserService.cs b/src/CMSMicroservice.WebApi/Services/UserService.cs index a27f2eb..72d0f53 100644 --- a/src/CMSMicroservice.WebApi/Services/UserService.cs +++ b/src/CMSMicroservice.WebApi/Services/UserService.cs @@ -34,7 +34,7 @@ public class UserService : UserContract.UserContractBase private readonly IApplicationDbContext _context; private readonly ICurrentUserService _currentUserService; private readonly IHashService _hashService; - private readonly IFileManagementService _fileManagementService; + private readonly CMSMicroservice.Application.Common.FileManager.IFileManager _fileManager; public UserService( IDispatchRequestToCQRS dispatchRequestToCQRS, @@ -42,14 +42,14 @@ public class UserService : UserContract.UserContractBase IApplicationDbContext context, ICurrentUserService currentUserService, IHashService hashService, - IFileManagementService fileManagementService) + CMSMicroservice.Application.Common.FileManager.IFileManager fileManager) { _dispatchRequestToCQRS = dispatchRequestToCQRS; _sender = sender; _context = context; _currentUserService = currentUserService; _hashService = hashService; - _fileManagementService = fileManagementService; + _fileManager = fileManager; } public override async Task CreateNewUser(CreateNewUserRequest request, ServerCallContext context) { @@ -65,6 +65,10 @@ public class UserService : UserContract.UserContractBase } public override async Task GetUser(GetUserRequest request, ServerCallContext context) { + // اگر Id ارسال نشده، از JWT بخون (برای کلاینت مشتری) + if (request.Id == 0) + request.Id = GetCurrentUserId(); + return await _dispatchRequestToCQRS.Handle(request, context); } public override async Task GetAllUserByFilter(GetAllUserByFilterRequest request, ServerCallContext context) @@ -335,15 +339,28 @@ public class UserService : UserContract.UserContractBase if (user == null) throw new RpcException(new Status(StatusCode.NotFound, "کاربر یافت نشد")); - // Upload to FMS + // Upload to local file manager var fileBytes = request.FileData.ToByteArray(); var fileName = $"avatar_{userId}_{DateTime.UtcNow.Ticks}"; var mime = request.FileMimeType ?? "image/jpeg"; - var avatarUrl = await _fileManagementService.UploadFileAsync( - "Avatars", fileBytes, mime, fileName, context.CancellationToken); - - if (string.IsNullOrEmpty(avatarUrl)) + try + { + var result = await _fileManager.UploadImageAsync( + "Avatars", fileBytes, mime, fileName, context.CancellationToken); + + // Update user avatar path in DB + user.AvatarPath = result.Main.Path; + await _context.SaveChangesAsync(context.CancellationToken); + + return new UploadCustomerAvatarResponse + { + Success = true, + Message = "تصویر پروفایل با موفقیت آپلود شد", + AvatarUrl = result.Main.Path + }; + } + catch (Exception ex) { return new UploadCustomerAvatarResponse { @@ -351,17 +368,6 @@ public class UserService : UserContract.UserContractBase Message = "خطا در آپلود فایل. لطفاً مجدد تلاش کنید" }; } - - // Update user avatar path in DB - user.AvatarPath = avatarUrl; - await _context.SaveChangesAsync(context.CancellationToken); - - return new UploadCustomerAvatarResponse - { - Success = true, - Message = "تصویر پروفایل با موفقیت آپلود شد", - AvatarUrl = avatarUrl - }; } public override async Task GetCustomerSettings(GetCustomerSettingsRequest request, ServerCallContext context) diff --git a/src/CMSMicroservice.WebApi/appsettings.Development.json b/src/CMSMicroservice.WebApi/appsettings.Development.json index 45353d8..ec93e69 100644 --- a/src/CMSMicroservice.WebApi/appsettings.Development.json +++ b/src/CMSMicroservice.WebApi/appsettings.Development.json @@ -1,5 +1,14 @@ { - "UseRealPaymentGateway": false, + "PaymentProvider": "pyms", + "PYMS": { + "Address": "https://pyms.se.kbs1.ir" + }, + "ZarinPal": { + "MerchantId": "00000000-0000-0000-0000-000000000000", + "UseSandbox": true + }, + "CmsBaseUrl": "https://localhost:32846", + "FrontOfficeBaseUrl": "https://localhost:5268", "JwtSecurityKey": "TvlZVx5TJaHs8e9HgUdGzhGP2CIidoI444nAj+8+g7c=", "JwtIssuer": "https://localhost", "JwtAudience": "https://localhost", diff --git a/src/CMSMicroservice.WebApi/appsettings.json b/src/CMSMicroservice.WebApi/appsettings.json index c3138d0..15e0f3b 100644 --- a/src/CMSMicroservice.WebApi/appsettings.json +++ b/src/CMSMicroservice.WebApi/appsettings.json @@ -1,5 +1,12 @@ { - "UseRealPaymentGateway": false, + "PaymentProvider": "pyms", + "PYMS": { + "Address": "http://pyms-svc.default.svc.cluster.local:80" + }, + "ZarinPal": { + "MerchantId": "6b098fc8-f490-47a1-aac3-1de1a1b84404", + "UseSandbox": true + }, "FMS": { "Address": "https://dl.afrino.co" },