diff --git a/backend/src/CCE.Api.External/Endpoints/NewsPublicEndpoints.cs b/backend/src/CCE.Api.External/Endpoints/NewsPublicEndpoints.cs index b68a3389..82e61d1f 100644 --- a/backend/src/CCE.Api.External/Endpoints/NewsPublicEndpoints.cs +++ b/backend/src/CCE.Api.External/Endpoints/NewsPublicEndpoints.cs @@ -1,6 +1,7 @@ using CCE.Api.Common.Extensions; using CCE.Application.Content.Public.Queries.GetPublicNewsBySlug; using CCE.Application.Content.Public.Queries.ListPublicNews; +using CCE.Application.Content.Public.Queries.GetPublicNewsById; using MediatR; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Routing; @@ -38,6 +39,16 @@ public static IEndpointRouteBuilder MapNewsPublicEndpoints(this IEndpointRouteBu .AllowAnonymous() .WithName("GetPublicNewsBySlug"); + news.MapGet("/{id:guid}", async ( + System.Guid id, + IMediator mediator, CancellationToken cancellationToken) => + { + var response = await mediator.Send(new GetPublicNewsByIdQuery(id), cancellationToken).ConfigureAwait(false); + return response.ToHttpResult(); + }) + .AllowAnonymous() + .WithName("GetPublicNewsById"); + return app; } } diff --git a/backend/src/CCE.Application/Content/Commands/CreateNews/CreateNewsCommandHandler.cs b/backend/src/CCE.Application/Content/Commands/CreateNews/CreateNewsCommandHandler.cs index 7a967e10..8f3d0086 100644 --- a/backend/src/CCE.Application/Content/Commands/CreateNews/CreateNewsCommandHandler.cs +++ b/backend/src/CCE.Application/Content/Commands/CreateNews/CreateNewsCommandHandler.cs @@ -60,6 +60,10 @@ public async Task> Handle(CreateNewsCommand request, Cancellat var topicNameAr = topic.FirstOrDefault()?.NameAr ?? string.Empty; var topicNameEn = topic.FirstOrDefault()?.NameEn ?? string.Empty; - return _messages.Ok(ListNewsQueryHandler.MapToDto(news, topicNameAr, topicNameEn), "CONTENT_CREATED"); + var users = await _db.Users.Where(u => u.Id == authorId.Value) + .ToListAsyncEither(cancellationToken).ConfigureAwait(false); + var authorName = users.FirstOrDefault() is { } u ? $"{u.FirstName} {u.LastName}".Trim() : string.Empty; + + return _messages.Ok(ListNewsQueryHandler.MapToDto(news, topicNameAr, topicNameEn, authorName), "CONTENT_CREATED"); } } diff --git a/backend/src/CCE.Application/Content/Commands/PublishNews/PublishNewsCommandHandler.cs b/backend/src/CCE.Application/Content/Commands/PublishNews/PublishNewsCommandHandler.cs index 408d7e23..06c76bfb 100644 --- a/backend/src/CCE.Application/Content/Commands/PublishNews/PublishNewsCommandHandler.cs +++ b/backend/src/CCE.Application/Content/Commands/PublishNews/PublishNewsCommandHandler.cs @@ -1,10 +1,12 @@ using CCE.Application.Common; using CCE.Application.Common.Interfaces; +using CCE.Application.Common.Pagination; using CCE.Application.Content.Dtos; using CCE.Application.Content.Queries.GetNewsById; using CCE.Application.Messages; using CCE.Domain.Common; using CCE.Domain.Content; +using CCE.Domain.Identity; using MediatR; namespace CCE.Application.Content.Commands.PublishNews; @@ -40,6 +42,10 @@ public async Task> Handle(PublishNewsCommand request, Cancella _db.SetExpectedRowVersion(news, expectedRowVersion); await _db.SaveChangesAsync(cancellationToken).ConfigureAwait(false); - return _messages.Ok(GetNewsByIdQueryHandler.MapToDto(news), "SUCCESS_OPERATION"); + var users = await _db.Users.Where(u => u.Id == news.AuthorId) + .ToListAsyncEither(cancellationToken).ConfigureAwait(false); + var authorName = users.FirstOrDefault() is { } u ? $"{u.FirstName} {u.LastName}".Trim() : string.Empty; + + return _messages.Ok(GetNewsByIdQueryHandler.MapToDto(news, authorName: authorName), "SUCCESS_OPERATION"); } } diff --git a/backend/src/CCE.Application/Content/Commands/UpdateNews/UpdateNewsCommandHandler.cs b/backend/src/CCE.Application/Content/Commands/UpdateNews/UpdateNewsCommandHandler.cs index 3bae6718..ed10207c 100644 --- a/backend/src/CCE.Application/Content/Commands/UpdateNews/UpdateNewsCommandHandler.cs +++ b/backend/src/CCE.Application/Content/Commands/UpdateNews/UpdateNewsCommandHandler.cs @@ -6,6 +6,7 @@ using CCE.Application.Messages; using CCE.Domain.Common; using CCE.Domain.Content; +using CCE.Domain.Identity; using MediatR; namespace CCE.Application.Content.Commands.UpdateNews; @@ -53,6 +54,10 @@ public async Task> Handle(UpdateNewsCommand request, Cancellat var topicNameAr = topic.FirstOrDefault()?.NameAr ?? string.Empty; var topicNameEn = topic.FirstOrDefault()?.NameEn ?? string.Empty; - return _messages.Ok(GetNewsByIdQueryHandler.MapToDto(news, topicNameAr, topicNameEn), "SUCCESS_OPERATION"); + var users = await _db.Users.Where(u => u.Id == news.AuthorId) + .ToListAsyncEither(cancellationToken).ConfigureAwait(false); + var authorName = users.FirstOrDefault() is { } u ? $"{u.FirstName} {u.LastName}".Trim() : string.Empty; + + return _messages.Ok(GetNewsByIdQueryHandler.MapToDto(news, topicNameAr, topicNameEn, authorName), "SUCCESS_OPERATION"); } } diff --git a/backend/src/CCE.Application/Content/Dtos/NewsDto.cs b/backend/src/CCE.Application/Content/Dtos/NewsDto.cs index ba18b79e..9afbc6a3 100644 --- a/backend/src/CCE.Application/Content/Dtos/NewsDto.cs +++ b/backend/src/CCE.Application/Content/Dtos/NewsDto.cs @@ -10,6 +10,7 @@ public sealed record NewsDto( string TopicNameAr, string TopicNameEn, System.Guid AuthorId, + string AuthorName, string? FeaturedImageUrl, System.DateTimeOffset? PublishedOn, bool IsFeatured, diff --git a/backend/src/CCE.Application/Content/Public/Queries/GetPublicNewsById/GetPublicNewsByIdQuery.cs b/backend/src/CCE.Application/Content/Public/Queries/GetPublicNewsById/GetPublicNewsByIdQuery.cs new file mode 100644 index 00000000..4367e62e --- /dev/null +++ b/backend/src/CCE.Application/Content/Public/Queries/GetPublicNewsById/GetPublicNewsByIdQuery.cs @@ -0,0 +1,7 @@ +using CCE.Application.Common; +using CCE.Application.Content.Dtos; +using MediatR; + +namespace CCE.Application.Content.Public.Queries.GetPublicNewsById; + +public sealed record GetPublicNewsByIdQuery(System.Guid Id) : IRequest>; diff --git a/backend/src/CCE.Application/Content/Public/Queries/GetPublicNewsById/GetPublicNewsByIdQueryHandler.cs b/backend/src/CCE.Application/Content/Public/Queries/GetPublicNewsById/GetPublicNewsByIdQueryHandler.cs new file mode 100644 index 00000000..28271821 --- /dev/null +++ b/backend/src/CCE.Application/Content/Public/Queries/GetPublicNewsById/GetPublicNewsByIdQueryHandler.cs @@ -0,0 +1,48 @@ +using CCE.Application.Common; +using CCE.Application.Common.Interfaces; +using CCE.Application.Common.Pagination; +using CCE.Application.Content.Dtos; +using CCE.Application.Messages; +using CCE.Domain.Community; +using CCE.Domain.Content; +using CCE.Domain.Identity; +using MediatR; + +namespace CCE.Application.Content.Public.Queries.GetPublicNewsById; + +public sealed class GetPublicNewsByIdQueryHandler : IRequestHandler> +{ + private readonly ICceDbContext _db; + private readonly MessageFactory _messages; + + public GetPublicNewsByIdQueryHandler(ICceDbContext db, MessageFactory messages) + { + _db = db; + _messages = messages; + } + + public async Task> Handle(GetPublicNewsByIdQuery request, CancellationToken cancellationToken) + { + var list = await _db.News.Where(n => n.Id == request.Id).ToListAsyncEither(cancellationToken).ConfigureAwait(false); + var news = list.SingleOrDefault(); + if (news is null) + return _messages.NewsNotFound(); + + var topics = await _db.Topics.Where(t => t.Id == news.TopicId) + .ToListAsyncEither(cancellationToken).ConfigureAwait(false); + var topic = topics.FirstOrDefault(); + + var users = await _db.Users.Where(u => u.Id == news.AuthorId) + .ToListAsyncEither(cancellationToken).ConfigureAwait(false); + var author = users.FirstOrDefault(); + var authorName = author is not null ? $"{author.FirstName} {author.LastName}".Trim() : string.Empty; + + return _messages.Ok(MapToDto(news, topic?.NameAr ?? string.Empty, topic?.NameEn ?? string.Empty, authorName), "SUCCESS_OPERATION"); + } + + internal static NewsDto MapToDto(News n, string topicNameAr = "", string topicNameEn = "", string authorName = "") => new( + n.Id, n.TitleAr, n.TitleEn, n.ContentAr, n.ContentEn, + n.TopicId, topicNameAr, topicNameEn, + n.AuthorId, authorName, n.FeaturedImageUrl, + n.PublishedOn, n.IsFeatured, n.IsPublished); +} diff --git a/backend/src/CCE.Application/Content/Queries/GetNewsById/GetNewsByIdQueryHandler.cs b/backend/src/CCE.Application/Content/Queries/GetNewsById/GetNewsByIdQueryHandler.cs index a7dafa1a..843f91cb 100644 --- a/backend/src/CCE.Application/Content/Queries/GetNewsById/GetNewsByIdQueryHandler.cs +++ b/backend/src/CCE.Application/Content/Queries/GetNewsById/GetNewsByIdQueryHandler.cs @@ -5,6 +5,7 @@ using CCE.Application.Messages; using CCE.Domain.Community; using CCE.Domain.Content; +using CCE.Domain.Identity; using MediatR; namespace CCE.Application.Content.Queries.GetNewsById; @@ -31,12 +32,17 @@ public async Task> Handle(GetNewsByIdQuery request, Cancellati .ToListAsyncEither(cancellationToken).ConfigureAwait(false); var topic = topics.FirstOrDefault(); - return _messages.Ok(MapToDto(news, topic?.NameAr ?? string.Empty, topic?.NameEn ?? string.Empty), "SUCCESS_OPERATION"); + var users = await _db.Users.Where(u => u.Id == news.AuthorId) + .ToListAsyncEither(cancellationToken).ConfigureAwait(false); + var author = users.FirstOrDefault(); + var authorName = author is not null ? $"{author.FirstName} {author.LastName}".Trim() : string.Empty; + + return _messages.Ok(MapToDto(news, topic?.NameAr ?? string.Empty, topic?.NameEn ?? string.Empty, authorName), "SUCCESS_OPERATION"); } - internal static NewsDto MapToDto(News n, string topicNameAr = "", string topicNameEn = "") => new( + internal static NewsDto MapToDto(News n, string topicNameAr = "", string topicNameEn = "", string authorName = "") => new( n.Id, n.TitleAr, n.TitleEn, n.ContentAr, n.ContentEn, n.TopicId, topicNameAr, topicNameEn, - n.AuthorId, n.FeaturedImageUrl, + n.AuthorId, authorName, n.FeaturedImageUrl, n.PublishedOn, n.IsFeatured, n.IsPublished); } diff --git a/backend/src/CCE.Application/Content/Queries/ListNews/ListNewsQueryHandler.cs b/backend/src/CCE.Application/Content/Queries/ListNews/ListNewsQueryHandler.cs index 6226d0b2..38d0ca30 100644 --- a/backend/src/CCE.Application/Content/Queries/ListNews/ListNewsQueryHandler.cs +++ b/backend/src/CCE.Application/Content/Queries/ListNews/ListNewsQueryHandler.cs @@ -5,6 +5,7 @@ using CCE.Application.Messages; using CCE.Domain.Community; using CCE.Domain.Content; +using CCE.Domain.Identity; using MediatR; namespace CCE.Application.Content.Queries.ListNews; @@ -41,20 +42,27 @@ public async Task>> Handle(ListNewsQuery request, .ToListAsyncEither(cancellationToken).ConfigureAwait(false); var topicById = topics.ToDictionary(t => t.Id); - return _messages.Ok(result.Map(n => MapToDto(n, topicById)), "ITEMS_LISTED"); + var authorIds = result.Items.Select(n => n.AuthorId).Distinct().ToList(); + var authors = await _db.Users.Where(u => authorIds.Contains(u.Id)) + .ToListAsyncEither(cancellationToken).ConfigureAwait(false); + var authorNameById = authors.ToDictionary(u => u.Id, u => $"{u.FirstName} {u.LastName}".Trim()); + + return _messages.Ok(result.Map(n => MapToDto(n, topicById, authorNameById)), "ITEMS_LISTED"); } - internal static NewsDto MapToDto(News n, Dictionary topicById) => new( + internal static NewsDto MapToDto(News n, Dictionary topicById, Dictionary authorNameById) => new( n.Id, n.TitleAr, n.TitleEn, n.ContentAr, n.ContentEn, n.TopicId, topicById.TryGetValue(n.TopicId, out var t) ? t.NameAr : string.Empty, topicById.TryGetValue(n.TopicId, out t) ? t.NameEn : string.Empty, - n.AuthorId, n.FeaturedImageUrl, + n.AuthorId, + authorNameById.TryGetValue(n.AuthorId, out var an) ? an : string.Empty, + n.FeaturedImageUrl, n.PublishedOn, n.IsFeatured, n.IsPublished); - internal static NewsDto MapToDto(News n, string topicNameAr = "", string topicNameEn = "") => new( + internal static NewsDto MapToDto(News n, string topicNameAr = "", string topicNameEn = "", string authorName = "") => new( n.Id, n.TitleAr, n.TitleEn, n.ContentAr, n.ContentEn, n.TopicId, topicNameAr, topicNameEn, - n.AuthorId, n.FeaturedImageUrl, + n.AuthorId, authorName, n.FeaturedImageUrl, n.PublishedOn, n.IsFeatured, n.IsPublished); }