using System.Text.Json; using Dapper; using Femto.Common.Infrastructure.DbConnection; using Femto.Modules.Blog.Application.Queries.GetPosts.Dto; using MediatR; namespace Femto.Modules.Blog.Application.Queries.GetPosts; public class GetPostsQueryHandler(IDbConnectionFactory connectionFactory) : IRequestHandler { public async Task Handle( GetPostsQuery query, CancellationToken cancellationToken ) { using var conn = connectionFactory.GetConnection(); var username = query.Author; var authorGuid = query.AuthorId; var cursor = query.After; var showPrivate = query.CurrentUserId is not null; var loadPostsResult = await conn.QueryAsync( """ select blog.post.id as PostId, blog.post.content as Content, blog.post.posted_on as PostedOn, blog.author.username as Username, blog.author.id as AuthorId, blog.post.possible_reactions as PossibleReactions from blog.post inner join blog.author on blog.author.id = blog.post.author_id where (@username is null or blog.author.username = @username) and (@postId is null or blog.post.id = @postId) and (@showPrivate or blog.post.is_public = true) and (@authorGuid is null or blog.author.id = @authorGuid) and (@cursor is null or blog.post.id < @cursor) order by blog.post.id desc limit @amount """, new { username, authorGuid, cursor, amount = query.PostId is not null ? 1 : query.Amount, showPrivate, postId = query.PostId, } ); var posts = loadPostsResult.ToList(); var postIds = posts.Select(p => p.PostId).ToList(); var loadMediaResult = await conn.QueryAsync( """ select pm.url as MediaUrl, pm.type as MediaType, pm.width as MediaWidth, pm.height as MediaHeight, pm.post_id as PostId from blog.post_media pm where pm.post_id = ANY (@postIds) order by pm.ordering """, new { postIds } ); var media = loadMediaResult.ToList(); var loadReactionsResult = await conn.QueryAsync( """ select pr.post_id as PostId, a.username as AuthorName, pr.emoji as Emoji, pr.created_at as CreatedOn from blog.post_reaction pr join blog.author a on a.id = pr.author_id where pr.post_id = ANY (@postIds) """, new { postIds } ); var reactions = loadReactionsResult.ToList(); var loadCommentsResult = await conn.QueryAsync( """ select pc.id as CommentId, pc.post_id as PostId, pc.content as Content, pc.created_at as PostedOn, a.username as AuthorName from blog.post_comment pc join blog.author a on pc.author_id = a.id where pc.post_id = ANY (@postIds) """, new { postIds } ); var comments = loadCommentsResult.ToList(); return new GetPostsQueryResult( posts .Select(p => new PostDto( p.PostId, p.Content, media .Where(m => m.PostId == p.PostId) .Select(m => new PostMediaDto( new Uri(m.MediaUrl), m.MediaWidth, m.MediaHeight )) .ToList(), p.PostedOn, new PostAuthorDto(p.AuthorId, p.Username), reactions .Where(r => r.PostId == p.PostId) .Select(r => new PostReactionDto(r.Emoji, r.AuthorName, r.CreatedAt)) .ToList(), !string.IsNullOrEmpty(p.PossibleReactions) ? JsonSerializer.Deserialize>(p.PossibleReactions)! : [], comments .Where(c => c.PostId == p.PostId) .Select(c => new PostCommentDto(c.AuthorName, c.Content, c.PostedOn)) .ToList() )) .ToList() ); } internal record LoadPostRow { public Guid PostId { get; init; } public string Content { get; init; } public DateTimeOffset PostedOn { get; init; } public string Username { get; init; } public Guid AuthorId { get; init; } public string? PossibleReactions { get; init; } } internal record LoadMediaRow { public string MediaUrl { get; init; } public string? MediaType { get; init; } public int? MediaWidth { get; init; } public int? MediaHeight { get; init; } public Guid PostId { get; init; } } internal record LoadReactionRow { public Guid PostId { get; init; } public string AuthorName { get; init; } public string Emoji { get; init; } public DateTimeOffset CreatedAt { get; init; } } internal record LoadCommentRow { public Guid CommentId { get; init; } public Guid PostId { get; init; } public string Content { get; init; } public DateTimeOffset PostedOn { get; init; } public string AuthorName { get; init; } } }