2502cbbda2
Build and Deploy to Kubernetes / build-and-deploy (push) Successful in 8m44s
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)
232 lines
10 KiB
C#
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;
|
|
}
|
|
}
|