| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116 |
- using Application.Abstractions.Hub;
- using Application.Abstractions.Notification;
- using Domain.Entities.Notifications.ValueObject;
- using Infrastructure.Hubs;
- using Web.Api.Common;
- using MediatR;
- using Microsoft.AspNetCore.SignalR;
- namespace Web.Api.Endpoints.Donation;
- /// <summary>크루 방송 종료 — 정산 + 크루원에게 결과 쪽지/알림</summary>
- internal sealed class CrewSessionEnd : IEndpoint
- {
- public void MapEndpoint(IEndpointRouteBuilder app)
- {
- app.MapPost("api/crew/session/end", async (
- Application.Features.Api.Crew.EndSession.Command body,
- ISender sender,
- IHubContext<AppHub, IAppHubClient> appHub,
- INotificationService notificationService,
- Application.Abstractions.Data.IAppDbContext db,
- CancellationToken ct
- ) => {
- // 종료 전 세션 정보 미리 조회
- var session = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions
- .FirstOrDefaultAsync(
- db.CrewSession.Where(s => s.ID == body.CrewSessionID), ct
- );
- if (session is null)
- {
- return CustomResults.Problem(SharedKernel.Results.Result.Failure(SharedKernel.Results.Error.NotFound("Session.NotFound", "세션을 찾을 수 없습니다.")));
- }
- var crew = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions
- .FirstOrDefaultAsync(
- Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions
- .AsNoTracking(db.Crew)
- .Where(c => c.ID == session.CrewID), ct
- );
- // 세션 종료 처리
- await sender.Send(body, ct);
- // 정산 결과 조회
- var summaries = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions
- .ToListAsync(
- Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions
- .AsNoTracking(db.CrewDonationSummary)
- .Where(s => s.CrewSessionID == body.CrewSessionID)
- .Join(db.CrewMember, s => s.CrewMemberID, m => m.ID, (s, m) => new {
- m.MemberID, m.Nickname, s.TotalAmount, s.DonationCount,
- s.ContributionRate, s.Rank
- })
- .OrderBy(x => x.Rank), ct
- );
- // 각 크루원에게 정산 알림 + 쪽지
- foreach (var summary in summaries)
- {
- var noteTitle = $"[{crew?.Name}] 크루 방송 정산 결과";
- var noteContent = $"방송: {session.Title}\n" +
- $"순위: {summary.Rank}위\n" +
- $"받은 후원: {summary.TotalAmount:N0}원 ({summary.DonationCount}건)\n" +
- $"기여율: {summary.ContributionRate:F1}%\n" +
- $"전체 후원: {session.TotalAmount:N0}원";
- // 시스템 쪽지 발송
- var note = Domain.Entities.Notes.Note.CreateSystem(
- summary.MemberID, noteTitle, noteContent,
- "CrewSession", body.CrewSessionID
- );
- db.Note.Add(note);
- // 알림 발송
- await notificationService.SendAsync(
- summary.MemberID,
- NotificationType.CrewEnded,
- "크루 방송 종료",
- $"'{session.Title}' 방송이 종료되었습니다. {summary.Rank}위, {summary.TotalAmount:N0}원",
- null, "CrewSession", body.CrewSessionID, null, ct
- );
- }
- await db.SaveChangesAsync(ct);
- // SignalR 브로드캐스트
- if (crew is not null)
- {
- var channel = await Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions
- .FirstOrDefaultAsync(
- Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions
- .AsNoTracking(db.Channel)
- .Where(c => c.ID == crew.ChannelID), ct
- );
- if (channel is not null)
- {
- await appHub.Clients.Group($"channel:{channel.SID}").ReceiveCrewEnded(new
- {
- CrewSessionID = body.CrewSessionID,
- Title = session.Title,
- TotalAmount = session.TotalAmount,
- Summaries = summaries.Select(s => new {
- s.Nickname, s.Rank, s.TotalAmount, s.ContributionRate
- })
- });
- }
- }
- return ApiResponse.Ok();
- })
- .WithTags("Crew")
- .RequireAuthorization();
- }
- }
|