using Application.Abstractions.Data;
using Application.Abstractions.Messaging;
using Microsoft.EntityFrameworkCore;
namespace Application.Features.Api.Forum.StockBoard.ListPosts;
///
/// 종목 가상보드 글 목록 (d2 §④ M2) — Post.StockCode 파티션 필터(최신순), StockBoardStats + 종목명 헤더 포함.
/// 삭제글 제외. 익명 열람.
///
internal sealed class Handler(IAppDbContext db) : IQueryHandler
{
public async Task Handle(Query request, CancellationToken ct)
{
var stockCode = request.StockCode?.Trim() ?? string.Empty;
var page = request.Page < 1 ? 1 : request.Page;
var perPage = request.PerPage == 0 ? (ushort)20 : request.PerPage;
var stockName = await db.Stock.AsNoTracking().Where(c => c.Code == stockCode).Select(c => c.Name).FirstOrDefaultAsync(ct);
var stats = await db.StockBoardStats.AsNoTracking().Where(c => c.StockCode == stockCode)
.Select(c => new { c.Posts, c.Comments, c.LastPostAt })
.FirstOrDefaultAsync(ct);
var header = new Response.StatsHeader(
stockCode,
stockName,
stats?.Posts ?? 0,
stats?.Comments ?? 0,
stats?.LastPostAt);
var baseQuery = db.Post.AsNoTracking().Where(c => c.StockCode == stockCode && !c.IsDeleted);
var total = await baseQuery.CountAsync(ct);
var predictionPostIDs = await db.PostPrediction.AsNoTracking()
.Where(c => c.StockCode == stockCode)
.Select(c => c.PostID)
.ToListAsync(ct);
var predictionSet = predictionPostIDs.ToHashSet();
var list = await baseQuery
.OrderByDescending(c => c.ID)
.Skip((page - 1) * perPage)
.Take(perPage)
.Select(c => new
{
c.ID,
c.StockCode,
c.Subject,
c.Thumbnail,
c.Name,
c.SID,
c.Views,
c.Likes,
c.Comments,
c.Images,
c.UpdatedAt,
c.CreatedAt
})
.ToListAsync(ct);
var startNum = total - ((page - 1) * perPage);
return new Response(
total,
header,
[..list.Select((c, i) => new Response.Row(
Num: startNum - i,
c.ID,
c.StockCode ?? stockCode,
c.Subject,
c.Thumbnail,
c.Name,
c.SID,
predictionSet.Contains(c.ID),
c.Views,
c.Likes,
c.Comments,
c.Images,
c.UpdatedAt,
c.CreatedAt
))]
);
}
}