ChannelIdentifierResolver.cs 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. using Application.Abstractions.Data;
  2. using Microsoft.EntityFrameworkCore;
  3. namespace Application.Abstractions.Channels;
  4. /// <summary>
  5. /// URL 파라미터(@handle 또는 YouTube Channel ID)를 내부 Channel.SID 로 변환하는 헬퍼
  6. /// - `@` 로 시작하면 현재 핸들 → SID 로 조회
  7. /// - 그 외에는 SID 로 간주 (YouTube Channel ID 24자)
  8. /// - 현재 핸들로 일치하는 채널이 없으면 이전 핸들 이력 조회 후 현재 핸들/SID 반환
  9. /// </summary>
  10. public static class ChannelIdentifierResolver
  11. {
  12. public enum IdentifierKind
  13. {
  14. Handle,
  15. Sid
  16. }
  17. /// <summary>
  18. /// <paramref name="ChannelSID"/> 가 null 이면 해당 식별자에 대응하는 채널이 없음을 의미.
  19. /// <paramref name="CanonicalHandle"/> 이 입력 핸들과 다르면 이전 핸들에서 변경된 채널임을 의미 (리다이렉트 대상).
  20. /// </summary>
  21. public readonly record struct ResolveResult(
  22. string? ChannelSID,
  23. string? CanonicalHandle,
  24. IdentifierKind Kind
  25. );
  26. public static IdentifierKind DetectKind(string identifier)
  27. {
  28. if (!string.IsNullOrEmpty(identifier) && identifier[0] == '@')
  29. {
  30. return IdentifierKind.Handle;
  31. }
  32. return IdentifierKind.Sid;
  33. }
  34. public static string NormalizeHandle(string identifier)
  35. {
  36. if (string.IsNullOrEmpty(identifier))
  37. {
  38. return string.Empty;
  39. }
  40. return identifier[0] == '@' ? identifier[1..] : identifier;
  41. }
  42. /// <summary>
  43. /// 입력 식별자를 Channel.SID 로 정규화.
  44. /// - SID 입력: DB 조회 없이 그대로 반환 (존재 여부는 후속 Handler 가 검증)
  45. /// - Handle 입력: 현재 핸들과 일치하는 채널 조회, 없으면 이력 테이블 조회하여 현재 핸들 반환
  46. /// </summary>
  47. public static async Task<ResolveResult> ResolveAsync(IAppDbContext db, string identifier, CancellationToken ct)
  48. {
  49. if (string.IsNullOrWhiteSpace(identifier))
  50. {
  51. return new ResolveResult(null, null, IdentifierKind.Sid);
  52. }
  53. var kind = DetectKind(identifier);
  54. if (kind == IdentifierKind.Sid)
  55. {
  56. return new ResolveResult(identifier, null, IdentifierKind.Sid);
  57. }
  58. var handle = NormalizeHandle(identifier);
  59. var current = await db.Channel.AsNoTracking()
  60. .Where(c => c.Handle == handle)
  61. .Select(c => new { c.SID, c.Handle })
  62. .FirstOrDefaultAsync(ct);
  63. if (current is not null)
  64. {
  65. return new ResolveResult(current.SID, current.Handle, IdentifierKind.Handle);
  66. }
  67. // 핸들 변경 이력 조회 — 이전 핸들이면 현재 핸들/SID 반환
  68. var historical = await db.ChannelHandleHistory.AsNoTracking()
  69. .Where(h => h.OldHandle == handle)
  70. .OrderByDescending(h => h.ChangedAt)
  71. .Select(h => new { h.Channel.SID, h.Channel.Handle })
  72. .FirstOrDefaultAsync(ct);
  73. if (historical is not null)
  74. {
  75. return new ResolveResult(historical.SID, historical.Handle, IdentifierKind.Handle);
  76. }
  77. return new ResolveResult(null, null, IdentifierKind.Handle);
  78. }
  79. }