namespace SharedKernel; public sealed class AppSettings { public AppSection App { get; init; } = new(); public ConnectionStringsSection ConnectionStrings { get; init; } = new(); public RedisSection Redis { get; init; } = new(); public SmtpSection SMTP { get; init; } = new(); public JwtSection JWT { get; init; } = new(); public ForwardedHeadersSection ForwardedHeaders { get; init; } = new(); public CorsPolicySection CorsPolicy { get; init; } = new(); public YouTubeSection YouTube { get; init; } = new(); public BackgroundJobsSection BackgroundJobs { get; init; } = new(); public EncryptionSection Encryption { get; init; } = new(); public NextcloudTalkSection NextcloudTalk { get; init; } = new(); public sealed class AppSection { public string Name { get; init; } = string.Empty; public string Company { get; init; } = string.Empty; public string BaseURL { get; init; } = string.Empty; public string ApiURL { get; init; } = string.Empty; public string FrontURL { get; init; } = string.Empty; } public sealed class ConnectionStringsSection { public string DefaultConnection { get; init; } = string.Empty; public int Timeout { get; init; } } public class RedisSection { public string DefaultConnection { get; init; } = string.Empty; public string CachePrefix { get; init; } = string.Empty; public string AuthTicketPrefix { get; init; } = string.Empty; public string DataProtectionKey { get; init; } = string.Empty; public TimeSpan DefaultKeyLifetime { get; init; } } public class SmtpSection { public string Host { get; init; } = default!; public int Port { get; init; } = 587; public string? User { get; init; } public string? Password { get; init; } public bool UseStartTls { get; init; } = true; public string FromEmail { get; init; } = default!; public string FromName { get; init; } = "no-reply"; } public class JwtSection { public string SecretKey { get; init; } = string.Empty; public string Issuer { get; init; } = string.Empty; public string Audience { get; init; } = string.Empty; public int AccessTokenExpiration { get; init; } public int RefreshTokenExpiration { get; init; } } public class ForwardedHeadersSection { public int? ForwardLimit { get; init; } = default; public List KnownProxies { get; init; } = []; public List KnownNetworks { get; init; } = []; } public class CorsPolicySection { public string Name { get; init; } = string.Empty; public long PreflightMaxAgeSeconds { get; init; } = 0; public List AllowedOrigins { get; init; } = []; } public class YouTubeSection { public string HubUrl { get; init; } = "https://pubsubhubbub.appspot.com/subscribe"; public string CallbackUrl { get; init; } = string.Empty; public string HmacSecret { get; init; } = string.Empty; public int FeedPollingIntervalMinutes { get; init; } = 3; } public class BackgroundJobsSection { public bool LiveChat { get; init; } = true; public bool PubSubRenewal { get; init; } = true; public bool FeedPolling { get; init; } = true; public bool LiveViewerPoller { get; init; } = true; public bool ChannelCacheRefresh { get; init; } = true; public bool YouTubeDailyAggregator { get; init; } = true; public bool RankingDailyAggregator { get; init; } = true; public bool YouTubeStaleDataPurge { get; init; } = true; public bool ApiPurchaseConfirm { get; init; } = true; } /// /// Nextcloud Talk 운영 알림 — 봇 계정(Basic auth)으로 OCS Chat API 호출. /// POST {BaseUrl}/ocs/v2.php/apps/spreed/api/v1/chat/{roomToken} /// AlertRoomToken = 이상 징후 방 / ActivityRoomToken = API 사용 로그 방. /// 봇 계정은 두 방의 참가자여야 하며, AppPassword 는 Nextcloud 앱 비밀번호(또는 계정 비밀번호). /// public class NextcloudTalkSection { public bool Enabled { get; init; } = false; /// 메시지 prefix 라벨 (예: Dev/Prod). dev·prod 배포가 둘 다 Production 환경명으로 돌므로 환경명 대신 사용. 비우면 EnvironmentName fallback. public string Label { get; init; } = string.Empty; public string BaseUrl { get; init; } = string.Empty; public string Username { get; init; } = string.Empty; public string AppPassword { get; init; } = string.Empty; public string AlertRoomToken { get; init; } = string.Empty; public string ActivityRoomToken { get; init; } = string.Empty; } /// /// 회원 PII(주소/연락처 등) 필드 암호화에 사용하는 AES-GCM 키 묶음. /// Keys 딕셔너리는 KeyVersion -> Base64(32바이트) 매핑. CurrentKeyVersion 은 신규 암호화에 사용할 버전. /// 키 회전 시 Keys 에 새 버전 추가 + CurrentKeyVersion 갱신. 복호화는 row 의 KeyVersion 컬럼으로 매핑하여 과거 키 사용. /// public class EncryptionSection { public int CurrentKeyVersion { get; init; } = 1; public Dictionary Keys { get; init; } = new(); } }