90 lines
4.1 KiB
Python
90 lines
4.1 KiB
Python
"""애플리케이션 설정 — 환경변수(.env)에서 로드.
|
|
|
|
pydantic-settings 로 타입 검증. 누락 시 조용한 실패 없이 명확하게 동작.
|
|
시간 윈도우(투표 오픈/마감), 스케줄러 시각, 외부 연동 키를 모두 여기서 관리.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from functools import lru_cache
|
|
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
model_config = SettingsConfigDict(
|
|
env_file=".env", env_file_encoding="utf-8", extra="ignore"
|
|
)
|
|
|
|
# ── 데이터베이스 ─────────────────────────────────────────
|
|
# docker-compose 의 db 서비스를 가리키는 기본값. 로컬 단독 실행 시 .env 로 덮어쓴다.
|
|
database_url: str = (
|
|
"postgresql+asyncpg://triplepick:triplepick@db:5432/triplepick"
|
|
)
|
|
|
|
# ── 일반 ────────────────────────────────────────────────
|
|
timezone: str = "Asia/Seoul" # 모든 경기 시각의 기준 (KST)
|
|
cors_origins: str = "*" # 콤마구분. nginx 프록시 사용 시 동일 출처라 보통 불필요.
|
|
public_origin: str = "http://localhost:8080" # 공유 딥링크 베이스
|
|
|
|
# ── 투표 윈도우 (도메인 규칙) ─────────────────────────────
|
|
# 오픈 = 킥오프 - VOTE_OPEN_HOURS_BEFORE (D-2 = 48h)
|
|
vote_open_hours_before: int = 48
|
|
# 마감 = 킥오프 1시간 전 (경기 시작 1시간 전까지 투표). lock 오프셋(분).
|
|
vote_lock_minutes_before: int = 60
|
|
# 데모: True 면 오픈 게이트 무시(항상 투표 가능). 운영 배포 시 False.
|
|
demo_force_open: bool = True
|
|
|
|
# ── 스케줄러 (별도 워커 프로세스 = "스케줄링 서버") ───────
|
|
# 경기 일정 동적 수집: 매일 KST 09:00 외부 소스 크롤링 → 경기·투표시간 갱신.
|
|
schedule_sync_hour: int = 9
|
|
schedule_sync_minute: int = 0
|
|
# 소스: openfootball(키 불필요·기본) | football-data(토큰 필요) | fallback(하드코딩만)
|
|
schedule_source: str = "openfootball"
|
|
schedule_url: str = (
|
|
"https://raw.githubusercontent.com/openfootball/worldcup.json/master/2026/worldcup.json"
|
|
)
|
|
schedule_group: str = "Group A" # 추적할 그룹 (openfootball 라벨)
|
|
football_data_token: str = "" # schedule_source=football-data 일 때
|
|
football_data_competition: str = "WC"
|
|
|
|
# 매일 AI 예측 생성 시각 (KST). 기능정의서: 매일 0시 1회 생성.
|
|
ai_generate_hour: int = 0
|
|
ai_generate_minute: int = 5
|
|
# 결과 메일: 경기 종료(결과 입력) 후 이 시간(분) 경과하면 발송. 기본 180분(3시간).
|
|
result_email_delay_minutes: int = 180
|
|
# 상태 전이 틱 주기(초): scheduled→open→locked 자동 갱신.
|
|
status_tick_seconds: int = 60
|
|
|
|
# ── 관리자 ──────────────────────────────────────────────
|
|
admin_api_token: str = "change-me-admin-token"
|
|
|
|
# ── 외부 연동: AI 3모델 (실연동) ─────────────────────────
|
|
openai_api_key: str = ""
|
|
openai_model: str = "gpt-4o"
|
|
anthropic_api_key: str = ""
|
|
anthropic_model: str = "claude-opus-4-8"
|
|
google_api_key: str = ""
|
|
google_model: str = "gemini-2.5-flash"
|
|
|
|
# ── 외부 연동: 이메일 (SMTP, 실연동) ─────────────────────
|
|
smtp_host: str = ""
|
|
smtp_port: int = 587
|
|
smtp_user: str = ""
|
|
smtp_password: str = ""
|
|
smtp_from: str = "TriplePick <no-reply@triplepick.app>"
|
|
smtp_starttls: bool = True
|
|
|
|
@property
|
|
def cors_origin_list(self) -> list[str]:
|
|
if self.cors_origins.strip() == "*":
|
|
return ["*"]
|
|
return [o.strip() for o in self.cors_origins.split(",") if o.strip()]
|
|
|
|
|
|
@lru_cache
|
|
def get_settings() -> Settings:
|
|
return Settings()
|
|
|
|
|
|
settings = get_settings()
|