| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120 |
- 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;
- /// <summary>후원 전송</summary>
- 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, IAppHubClient> appHub,
- IHubContext<DonationHub, IDonationHubClient> 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();
- }
- }
|