RemoteReorder.cs 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Hub;
  3. using Domain.Entities.Donations.ValueObject;
  4. using Infrastructure.Hubs;
  5. using Web.Api.Common;
  6. using Web.Api.Extensions;
  7. using Microsoft.AspNetCore.SignalR;
  8. using Microsoft.EntityFrameworkCore;
  9. using System.Security.Claims;
  10. namespace Web.Api.Endpoints.Donation;
  11. /// <summary>리모콘 — 큐 순서 변경 (두 알림 위치 swap, 위젯+리모콘 broadcast)</summary>
  12. internal sealed class RemoteReorder : IEndpoint
  13. {
  14. public sealed record Request(string WidgetToken, int AlertIDA, int AlertIDB);
  15. public void MapEndpoint(IEndpointRouteBuilder app)
  16. {
  17. app.MapPost("api/donation/remote/reorder", async (
  18. Request request,
  19. ClaimsPrincipal user,
  20. IAppDbContext db,
  21. IHubContext<DonationHub, IDonationHubClient> donationHub,
  22. CancellationToken ct
  23. ) => {
  24. var memberID = user.GetRequiredMemberID();
  25. if (string.IsNullOrWhiteSpace(request.WidgetToken) || request.AlertIDA == request.AlertIDB)
  26. {
  27. return ApiResponse.Fail(StatusCodes.Status400BadRequest, "잘못된 요청입니다.");
  28. }
  29. // 권한 확인 — 본인 채널의 WidgetToken 만 reorder 가능
  30. var channel = await db.Channel.AsNoTracking()
  31. .Where(c => c.WidgetToken == request.WidgetToken)
  32. .Select(c => new { c.ID, c.MemberID })
  33. .FirstOrDefaultAsync(ct);
  34. if (channel is null || channel.MemberID != memberID)
  35. {
  36. return ApiResponse.Fail(StatusCodes.Status403Forbidden, "권한이 없습니다.");
  37. }
  38. // 방어적 검증 — 두 alertID 가 본 채널의 Queued 상태인지 확인 (재생/완료/스킵 항목 순서 조작 차단)
  39. var alerts = await db.DonationAlert.AsNoTracking()
  40. .Where(a => (a.ID == request.AlertIDA || a.ID == request.AlertIDB) && a.Donation!.ChannelID == channel.ID)
  41. .Select(a => new { a.ID, a.Status })
  42. .ToListAsync(ct);
  43. if (alerts.Count != 2 || alerts.Any(a => a.Status != AlertStatus.Queued))
  44. {
  45. return ApiResponse.Fail(StatusCodes.Status400BadRequest, "대기 중인 알림만 순서 변경이 가능합니다.");
  46. }
  47. // Widget+Remote 양쪽 그룹에 swap broadcast — 클라이언트가 자기 큐/표시 순서 swap
  48. await donationHub.Clients.Group(request.WidgetToken).ReceiveQueueReorder(new
  49. {
  50. swap = new[] { request.AlertIDA, request.AlertIDB }
  51. });
  52. return ApiResponse.Ok();
  53. })
  54. .WithTags("DonationRemote")
  55. .RequireAuthorization();
  56. }
  57. }