Files
CMS/src/CMSMicroservice.WebApi/Services/BlogPostService.cs
T
masoodafar-web 2502cbbda2
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m44s
feat: integrate PYMS payment gateway, add blog/sitepage/image services, local file manager
Payment Gateway:
- Add PYMSPaymentService: IPaymentGatewayService via gRPC to PYMS microservice
- Add ZarinPalPaymentService: direct ZarinPal integration (backup)
- Register 'pyms' payment provider in DI ConfigureServices
- Add PYMS proto files (pyms_transaction.proto, pyms_public_messages.proto)
- Fix VerifyDiscountWalletCharge: pass 'OK' as status instead of Authority
- Update appsettings: PaymentProvider=pyms, sandbox mode, merchant ID

Blog System:
- Add BlogCategory, BlogPost, BlogPostImage entities and CQRS
- Add proto files and gRPC services for blog management
- Add Mapster profiles for blog responses

Content Management:
- Add SitePage entity and CQRS for static pages
- Add proto and gRPC service for site pages

Image/File Management:
- Add LocalFileManager with disk storage + base64 serving + FMS fallback
- Add ImagePathResolverInterceptor for gRPC responses
- Add ImageResolverService for explicit image resolution
- Add UploadsController for public file serving with FMS fallback
- Add PaymentCallbackController for discount order payment callbacks

Database:
- Add blog and content entity migrations
- Remove ImagePath MaxLength constraints
- Remove old FileManagementService (replaced by LocalFileManager)
2026-02-15 23:01:16 +03:30

232 lines
10 KiB
C#

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<CreateBlogPostResponse> 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<Empty> 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<Empty> DeleteBlogPost(DeleteBlogPostRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<DeleteBlogPostRequest, DeleteBlogPostCommand>(request, context);
}
public override async Task<GetBlogPostResponse> 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<GetBlogPostResponse> 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<GetAllBlogPostsResponse> 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<GetAllBlogPostsResponse> 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<GetAllBlogPostsResponse> 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<PublishBlogPostResponse> PublishBlogPost(PublishBlogPostRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<PublishBlogPostRequest, PublishBlogPostCommand, PublishBlogPostResponse>(request, context);
}
public override async Task<ArchiveBlogPostResponse> ArchiveBlogPost(ArchiveBlogPostRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<ArchiveBlogPostRequest, ArchiveBlogPostCommand, ArchiveBlogPostResponse>(request, context);
}
public override async Task<Empty> IncrementViewCount(IncrementViewCountRequest request, ServerCallContext context)
{
return await _dispatchRequestToCQRS.Handle<IncrementViewCountRequest, IncrementViewCountCommand>(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<ProtoMetaData>()
};
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;
}
}