femto-backend/Femto.Modules.Blog/Application/Queries/GetPosts/GetPostsQueryHandler.cs
2025-08-10 21:21:40 +02:00

173 lines
5.8 KiB
C#

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<GetPostsQuery, GetPostsQueryResult>
{
public async Task<GetPostsQueryResult> 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<LoadPostRow>(
"""
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<LoadMediaRow>(
"""
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<LoadReactionRow>(
"""
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<LoadCommentRow>(
"""
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<IEnumerable<string>>(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; }
}
}