using Application.Abstractions.Hub;
using Infrastructure.Hubs;
using Web.Api.Common;
using Web.Api.Extensions;
using MediatR;
using Microsoft.AspNetCore.SignalR;
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;
namespace Web.Api.Endpoints.Donation;
/// 후원 전송
internal sealed class Send : IEndpoint
{
public void MapEndpoint(IEndpointRouteBuilder app)
{
/// 후원 전송 (지갑 차감 → Donation 생성 → Alert 생성 → OBS 위젯 브로드캐스트)
app.MapPost("api/donation/send", async (
Application.Features.Api.Donation.Send.Command body,
ClaimsPrincipal user,
ISender sender,
IHubContext appHub,
IHubContext donationHub,
Application.Abstractions.Data.IAppDbContext db,
CancellationToken ct
) => {
var memberID = user.GetRequiredMemberID();
var command = body with { SponsorMemberID = memberID };
var result = await sender.Send(command, ct);
if (result.IsFailure)
{
return CustomResults.Problem(result);
}
var data = result.Value;
// 채널 정보 (widgetToken, ChannelID, ChannelName) 조회
var channelID = body.ChannelID > 0 ? body.ChannelID :
(body.ChannelSID != null ?
await db.Channel.AsNoTracking().Where(c => c.SID == body.ChannelSID).Select(c => c.ID).FirstOrDefaultAsync(ct) : 0);
var channelMeta = await db.Channel.AsNoTracking()
.Where(c => c.ID == channelID)
.Select(c => new { c.WidgetToken, c.Name })
.FirstOrDefaultAsync(ct);
// 크루원 닉네임 조회 (크루 후원인 경우)
string? crewMemberNickname = null;
if (body.CrewMemberID.HasValue)
{
crewMemberNickname = await db.CrewMember.AsNoTracking()
.Where(m => m.ID == body.CrewMemberID.Value)
.Select(m => m.Nickname)
.FirstOrDefaultAsync(ct);
}
// ── OBS 위젯 알림 브로드캐스트 (DonationHub.ReceiveAlert) ──
if (channelMeta is not null && !string.IsNullOrEmpty(channelMeta.WidgetToken))
{
var alertPayload = new
{
alertID = data.AlertID,
donationID = data.DonationID,
correlationID = data.CorrelationID,
sponsorMemberID = memberID,
sendName = body.SendName,
amount = data.Amount,
netAmount = data.NetAmount,
message = body.Message,
channelID = channelID,
channelName = channelMeta.Name,
crewMemberID = body.CrewMemberID,
crewMemberNickname = crewMemberNickname,
isTest = body.IsTest,
createdAt = DateTime.UtcNow
};
await donationHub.Clients.Group(channelMeta.WidgetToken).ReceiveAlert(alertPayload);
// ⚠️ Goal/Rank/Crew broadcast는 알림 재생 종료 시점(DonationHub.AlertCompleted)에 처리됨
}
// ── 크루 세션 후원인 경우 추가 브로드캐스트 ──
if (body.CrewSessionID.HasValue && body.CrewMemberID.HasValue)
{
// 크루 순위 업데이트 → DonationHub (OBS 위젯)
var rankResult = await sender.Send(
new Application.Features.Api.Crew.GetCrewRanking.Query(body.CrewSessionID.Value), ct
);
if (rankResult.IsSuccess && channelMeta is not null && !string.IsNullOrEmpty(channelMeta.WidgetToken))
{
var rankData = rankResult.Value;
await donationHub.Clients.Group(channelMeta.WidgetToken).ReceiveCrewUpdate(new
{
rankData.List,
rankData.TotalAmount
});
}
// 크루원에게 토스트 알림 → AppHub
var crewMember = await db.CrewMember.AsNoTracking()
.Where(m => m.ID == body.CrewMemberID.Value).FirstOrDefaultAsync(ct);
if (crewMember is not null)
{
await appHub.Clients.Group($"member:{crewMember.MemberID}").ReceiveCrewToast(new
{
SendName = body.SendName,
Amount = body.Amount,
Message = body.Message,
CrewMemberNickname = crewMember.Nickname
});
}
}
return ApiResponse.Ok(data);
})
.WithTags("Donation")
.RequireAuthorization();
}
}