using Application.Abstractions.Data;
using Application.Abstractions.Hub;
using Domain.Entities.Donations.ValueObject;
using Infrastructure.Hubs;
using Web.Api.Common;
using Web.Api.Extensions;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;
namespace Web.Api.Endpoints.Donation;
/// 리모콘 — 큐 순서 변경 (두 알림 위치 swap, 위젯+리모콘 broadcast)
internal sealed class RemoteReorder : IEndpoint
{
public sealed record Request(string WidgetToken, int AlertIDA, int AlertIDB);
public void MapEndpoint(IEndpointRouteBuilder app)
{
app.MapPost("api/donation/remote/reorder", async (
Request request,
ClaimsPrincipal user,
IAppDbContext db,
IHubContext donationHub,
CancellationToken ct
) => {
var memberID = user.GetRequiredMemberID();
if (string.IsNullOrWhiteSpace(request.WidgetToken) || request.AlertIDA == request.AlertIDB)
{
return ApiResponse.Fail(StatusCodes.Status400BadRequest, "잘못된 요청입니다.");
}
// 권한 확인 — 본인 채널의 WidgetToken 만 reorder 가능
var channel = await db.Channel.AsNoTracking()
.Where(c => c.WidgetToken == request.WidgetToken)
.Select(c => new { c.ID, c.MemberID })
.FirstOrDefaultAsync(ct);
if (channel is null || channel.MemberID != memberID)
{
return ApiResponse.Fail(StatusCodes.Status403Forbidden, "권한이 없습니다.");
}
// 방어적 검증 — 두 alertID 가 본 채널의 Queued 상태인지 확인 (재생/완료/스킵 항목 순서 조작 차단)
var alerts = await db.DonationAlert.AsNoTracking()
.Where(a => (a.ID == request.AlertIDA || a.ID == request.AlertIDB) && a.Donation!.ChannelID == channel.ID)
.Select(a => new { a.ID, a.Status })
.ToListAsync(ct);
if (alerts.Count != 2 || alerts.Any(a => a.Status != AlertStatus.Queued))
{
return ApiResponse.Fail(StatusCodes.Status400BadRequest, "대기 중인 알림만 순서 변경이 가능합니다.");
}
// Widget+Remote 양쪽 그룹에 swap broadcast — 클라이언트가 자기 큐/표시 순서 swap
await donationHub.Clients.Group(request.WidgetToken).ReceiveQueueReorder(new
{
swap = new[] { request.AlertIDA, request.AlertIDB }
});
return ApiResponse.Ok();
})
.WithTags("DonationRemote")
.RequireAuthorization();
}
}