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 FeaturesSection Features { get; init; } = new(); public StockDataSection StockData { get; init; } = new(); public KRXCoKrSection KRXCoKr { get; init; } = new(); public OAuthSection OAuth { 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 YouTubeStaleDataPurge { get; init; } = true; /// 결제 자동 대사 배치 (Toss NeedsReconciliation 재확인 — PaymentReconcileService). Features:Channel 게이트와 무관. public bool PaymentReconcile { get; init; } = true; /// 모의투자 체결·스냅샷 배치 (PaperFillService — d4 M2). Features:Channel 게이트와 무관, 기본 false (StockData 적재 가동 후 활성화). public bool PaperFill { get; init; } = false; /// 예측 채점 배치 (PredictionSettlementService — d2 M4). Web.Api 전용, 기본 false (StockData T+1 종가 적재 가동 후 활성화). public bool PredictionSettlement { get; init; } = false; } /// /// 기능 단위 활성화 플래그. 코드/스키마는 보존하고 노출만 차단하는 soft-off 스위치. /// Channel=false 시 채널/스튜디오/YouTube 관련 API 와 백그라운드 서비스 전체 비활성 (기본 false). /// public class FeaturesSection { public bool Channel { get; init; } = false; } /// /// 회원 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(); } /// /// 소셜 로그인 OAuth 설정 (개미투자 D5 — Naver/Kakao). 키는 각 개발자센터 발급 후 채움 (빈 값이면 해당 provider 로그인 비활성). /// Google 은 DB Config(External.GoogleClientId) 현행 유지 — 여기 포함하지 않는다. /// public class OAuthSection { public OAuthProviderSection Naver { get; init; } = new(); public OAuthProviderSection Kakao { get; init; } = new(); public class OAuthProviderSection { /// 네이버: Client ID / 카카오: REST API 키 public string ClientId { get; init; } = string.Empty; /// 네이버: Client Secret(필수) / 카카오: 콘솔에서 활성화한 경우만 (빈 값이면 미전송) public string ClientSecret { get; init; } = string.Empty; /// 기본 redirect URI — 요청에서 미전달 시 사용. 개발자센터에 등록한 값과 일치해야 함 public string RedirectUri { get; init; } = string.Empty; /// /// 카카오 Admin 키 — "연결 끊기(unlink)" webhook 요청의 Authorization 헤더 대조에 사용. /// 카카오 개발자콘솔의 앱 Admin 키를 그대로 넣는다 (빈 값이면 disconnect 검증 비활성 → 항상 거부). /// 네이버는 사용하지 않음. /// public string AdminKey { get; init; } = string.Empty; } } /// /// 주식 데이터 수집 배치 설정 (개미투자 D1). 플래그 기본 false — API 키 발급/운영 결정 후 활성화. /// Features:Channel 게이트와 무관하게 별도 등록 (AddStockDataServices). /// public class StockDataSection { /// 종목 마스터 동기화 배치 (StockMasterSyncService) 활성화 public bool MasterSync { get; init; } = false; /// T+1 일별 시세 수집 배치 (DailyPriceSyncService) 활성화 public bool DailyPriceSync { get; init; } = false; /// 마스터 동기화 실행 시각 (KST, "HH:mm") public string MasterSyncTime { get; init; } = "07:30"; /// 일별 시세 수집 실행 시각 (KST, "HH:mm") — 금융위 API 는 영업일+1 13시 이후 반영 public string DailyPriceSyncTime { get; init; } = "13:10"; public DataGoKrSection DataGoKr { get; init; } = new(); /// 공공데이터포털 (data.go.kr) 금융위 API public class DataGoKrSection { /// 서비스 키 (Decoding 원본 키 — 요청 시 URL 인코딩). 비어 있으면 배치는 로그만 남기고 skip. public string ServiceKey { get; init; } = string.Empty; public string BaseUrl { get; init; } = "https://apis.data.go.kr"; /// 페이지당 행 수 (numOfRows) public int PageSize { get; init; } = 1000; } } /// /// KRX 한국거래소 OpenAPI (data-dbg.krx.co.kr) 지수 데이터 수집 설정 (개미투자 D1). /// data.go.kr(StockDataSection)과는 별개 시스템 — 별도 API 키 + AUTH_KEY 헤더 인증. /// IndexSync 기본 false — API 키 발급/운영 결정 후 활성화. Features:Channel 게이트와 무관하게 AddStockDataServices 에서 등록. /// public class KRXCoKrSection { /// KRX OpenAPI 인증 키 (AUTH_KEY 헤더). 비어 있으면 배치는 로그만 남기고 skip. tracked 파일엔 빈 값만. public string ApiKey { get; init; } = string.Empty; public string BaseUrl { get; init; } = "https://data-dbg.krx.co.kr"; /// 지수 일별시세 수집 배치 (IndexPriceSyncService) 활성화 public bool IndexSync { get; init; } = false; /// 지수 수집 실행 시각 (KST, "HH:mm") — KRX 지수는 장 마감(15:30) 이후 확정, 여유 두고 18:30 public string IndexSyncTime { get; init; } = "18:30"; /// 주식(종목) 마스터 + 일별매매 수집 배치 (KrxStockMasterSyncService/KrxDailyPriceSyncService) 활성화 — D1 KRX 주식 데이터 수집 public bool StockSync { get; init; } = false; /// 종목 마스터 동기화 실행 시각 (KST, "HH:mm") — 장 시작 전 07:40 public string MasterSyncTime { get; init; } = "07:40"; /// 주식 일별매매 수집 실행 시각 (KST, "HH:mm") — KRX 일별매매는 장 마감(15:30) 이후 확정, 여유 두고 18:10 public string StockSyncTime { get; init; } = "18:10"; /// 증권상품(ETF/ETN/ELW) 일별매매 수집 배치 (KrxEtpSyncService) 활성화 — KRX 증권상품(ETF/ETN/ELW) 수집 public bool EtpSync { get; init; } = false; /// 증권상품(ETF/ETN/ELW) 일별매매 수집 실행 시각 (KST, "HH:mm") — 장 마감(15:30) 이후 확정, 주식 수집(18:10) 뒤 여유 두고 18:20 public string EtpSyncTime { get; init; } = "18:20"; /// 신주인수권증권/증서 일별매매 수집 배치 (KrxWarrantSyncService) 활성화 — sw_bydd_trd(증권)+sr_bydd_trd(증서) public bool WarrantSync { get; init; } = false; /// 신주인수권증권/증서 일별매매 수집 실행 시각 (KST, "HH:mm") — 증권상품 수집(18:20) 뒤 여유 두고 18:25 public string WarrantSyncTime { get; init; } = "18:25"; } }