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 orderBy = query.Direction is GetPostsDirection.Backward ? "desc" : "asc"; var pageFilter = query.Direction is GetPostsDirection.Backward ? "<=" : ">="; var username = query.Author; var authorGuid = query.AuthorId; var cursor = query.From; var showPrivate = query.CurrentUserId is not null; // lang=sql var sql = $$""" with page as ( select blog.post.*, blog.author.username as Username, blog.author.id as AuthorId 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 (@showPrivate or blog.post.is_public = true) and (@authorGuid is null or blog.author.id = @authorGuid) and (@cursor is null or blog.post.id {{pageFilter}} @cursor) order by blog.post.id {{orderBy}} limit @amount ) select page.id as PostId, page.content as Content, blog.post_media.url as MediaUrl, blog.post_media.width as MediaWidth, blog.post_media.height as MediaHeight, page.posted_on as PostedOn, page.Username, page.AuthorId from page left join blog.post_media on blog.post_media.post_id = page.id order by page.id {{orderBy}} """; var result = await conn.QueryAsync( sql, new { username, authorGuid, cursor, // load an extra one to take for the cursor amount = query.Amount + 1, showPrivate, } ); var rows = result.ToList(); var posts = rows.GroupBy(row => row.PostId) .Select(group => { var postId = group.Key; var post = group.First(); var media = group .Select(row => { if (row.MediaUrl is not null) { return new PostMediaDto( new Uri(row.MediaUrl), row.MediaHeight, row.MediaHeight ); } else return null; }) .OfType() .ToList(); return new PostDto( postId, post.Content, media, post.PostedOn, new PostAuthorDto(post.AuthorId, post.Username) ); }) .ToList(); var next = rows.Count >= query.Amount ? rows.LastOrDefault()?.PostId : null; return new GetPostsQueryResult(posts, next); } internal class QueryResult { public Guid PostId { get; set; } public string Content { get; set; } public string? MediaUrl { get; set; } public string? MediaType { get; set; } public int? MediaWidth { get; set; } public int? MediaHeight { get; set; } public DateTimeOffset PostedOn { get; set; } public Guid AuthorId { get; set; } public string Username { get; set; } } }