164 lines
5.5 KiB
C#
164 lines
5.5 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.From;
|
|
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 (@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,
|
|
// load an extra one to take for the cursor
|
|
amount = query.Amount + 1,
|
|
showPrivate,
|
|
}
|
|
);
|
|
|
|
var loadedPosts = loadPostsResult.ToList();
|
|
var posts = loadedPosts.Take(query.Amount).ToList();
|
|
var next = loadedPosts.LastOrDefault()?.PostId;
|
|
|
|
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 loadReactionsResult = await conn.QueryAsync<LoadReactionRow>(
|
|
"""
|
|
select
|
|
pr.post_id as PostId,
|
|
pr.author_id as AuthorId,
|
|
pr.emoji as Emoji
|
|
from blog.post_reaction pr
|
|
where pr.post_id = ANY (@postIds)
|
|
""",
|
|
new { postIds }
|
|
);
|
|
|
|
var reactionsByPostId = loadReactionsResult
|
|
.GroupBy(r => r.PostId)
|
|
.ToDictionary(
|
|
group => group.Key,
|
|
group =>
|
|
group
|
|
.GroupBy(
|
|
r => r.Emoji,
|
|
(key, g) =>
|
|
{
|
|
var reactions = g.ToList();
|
|
return new PostReactionDto(
|
|
key,
|
|
reactions.Count,
|
|
reactions.Any(r => r.AuthorId == query.CurrentUserId)
|
|
);
|
|
}
|
|
)
|
|
.ToList()
|
|
);
|
|
|
|
var mediaByPostId = loadMediaResult
|
|
.GroupBy(m => m.PostId)
|
|
.ToDictionary(
|
|
g => g.Key,
|
|
g =>
|
|
g.Select(m => new PostMediaDto(
|
|
new Uri(m.MediaUrl),
|
|
m.MediaWidth,
|
|
m.MediaHeight
|
|
))
|
|
.ToList()
|
|
);
|
|
|
|
return new GetPostsQueryResult(
|
|
posts
|
|
.Select(p => new PostDto(
|
|
p.PostId,
|
|
p.Content,
|
|
mediaByPostId.TryGetValue(p.PostId, out var mediaDtos) ? mediaDtos : [],
|
|
p.PostedOn,
|
|
new PostAuthorDto(p.AuthorId, p.Username),
|
|
reactionsByPostId.TryGetValue(p.PostId, out var reactionDtos)
|
|
? reactionDtos.ToList()
|
|
: [],
|
|
!string.IsNullOrEmpty(p.PossibleReactions)
|
|
? JsonSerializer.Deserialize<IEnumerable<string>>(p.PossibleReactions)!
|
|
: []
|
|
))
|
|
.ToList(),
|
|
next
|
|
);
|
|
}
|
|
|
|
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 Guid AuthorId { get; init; }
|
|
public string Emoji { get; init; }
|
|
}
|
|
}
|