from __future__ import annotations import os from functools import lru_cache from pathlib import Path ROOT_DIR = Path(__file__).resolve().parent.parent def _read_env_file(path: Path) -> dict[str, str]: values: dict[str, str] = {} if not path.exists(): return values for raw_line in path.read_text(encoding="utf-8").splitlines(): line = raw_line.strip() if not line or line.startswith("#") or "=" not in line: continue key, value = line.split("=", 1) key = key.strip() value = value.strip().strip('"').strip("'") if key: values[key] = value return values def _load_env_files() -> None: loaded: dict[str, str] = {} for path in [ ROOT_DIR / ".env", ROOT_DIR / ".env.local", ROOT_DIR / "backend" / ".env", ROOT_DIR / "backend" / ".env.local", ]: loaded.update(_read_env_file(path)) for key, value in loaded.items(): os.environ.setdefault(key, value) _load_env_files() def _split_csv(value: str) -> list[str]: return [item.strip() for item in value.split(",") if item.strip()] def _bool_env(name: str, default: str = "false") -> bool: return os.getenv(name, default).lower() in {"1", "true", "yes", "on"} class Settings: def __init__(self) -> None: self.pb_url = ( os.getenv("PB_INTERNAL_URL") or os.getenv("POCKETBASE_INTERNAL_URL") or os.getenv("PB_URL") or os.getenv("POCKETBASE_URL") or "http://127.0.0.1:8090" ).rstrip("/") self.pb_admin_email = ( os.getenv("PB_ADMIN_EMAIL") or os.getenv("POCKETBASE_EMAIL") or "admin@nomadcna.local" ) self.pb_admin_password = ( os.getenv("PB_ADMIN_PASSWORD") or os.getenv("POCKETBASE_PASSWORD") or "NomadCNA123456" ) self.cookie_name = os.getenv("API_COOKIE_NAME", "pb_session") self.frontend_origins = _split_csv( os.getenv( "FRONTEND_ORIGINS", "http://localhost:3001,http://127.0.0.1:3001", ) ) self.upload_dir = Path(os.getenv("UPLOAD_DIR", "backend/uploads")) self.app_env = os.getenv("APP_ENV", "development").strip().lower() self.dev_auto_pay = _bool_env("DEV_AUTO_PAY", "false") self.site_id = os.getenv("SITE_ID") or os.getenv("NEXT_PUBLIC_SITE_ID") or "meetup" self.base_url = ( os.getenv("API_PUBLIC_BASE_URL") or os.getenv("BASE_URL") or os.getenv("PAYMENT_API_URL") or os.getenv("NEXT_PUBLIC_API_BASE_URL") or "http://127.0.0.1:8000" ).rstrip("/") if self.base_url == "/api": self.base_url = (os.getenv("NEXT_PUBLIC_SITE_URL") or "http://127.0.0.1:3001").rstrip("/") self.upload_public_base_url = ( os.getenv("UPLOAD_PUBLIC_BASE_URL") or os.getenv("API_PUBLIC_BASE_URL") or os.getenv("BASE_URL") or self.base_url ).rstrip("/") self.email_auth_password = os.getenv("EMAIL_AUTH_PASSWORD", "NomadCNAEmailLogin2026!") self.google_client_id = os.getenv("GOOGLE_CLIENT_ID") or os.getenv("NEXT_PUBLIC_GOOGLE_CLIENT_ID") or "" self.payment_provider = os.getenv("PAYMENT_PROVIDER", "zpay").lower() self.payment_default_amount = int(os.getenv("PAYMENT_DEFAULT_AMOUNT", "100") or "100") self.payment_join_amount = int(os.getenv("PAYMENT_JOIN_AMOUNT", str(self.payment_default_amount)) or str(self.payment_default_amount)) self.payment_join_type = os.getenv("PAYMENT_JOIN_TYPE", "meetup") self.zpay_pid = os.getenv("ZPAY_PID", "2025121809351743") self.zpay_key = os.getenv("ZPAY_KEY", "tpEi7wWIWI2kXiYVTpIG6j7it0mjVW89") self.zpay_submit_url = os.getenv("ZPAY_SUBMIT_URL", "https://zpayz.cn/submit.php") self.zpay_mapi_url = os.getenv("ZPAY_MAPI_URL", "https://zpayz.cn/mapi.php") self.zpay_query_url = os.getenv("ZPAY_QUERY_URL", "https://zpayz.cn/api.php") self.xorpay_aid = os.getenv("XORPAY_AID", "8220") self.xorpay_secret = os.getenv("XORPAY_SECRET", "afcacd99570945f88de62624aaa3578e") self.xorpay_cashier_url = os.getenv("XORPAY_CASHIER_URL", "https://xorpay.com/api/cashier") self.xorpay_query_url = os.getenv("XORPAY_QUERY_URL", "https://xorpay.com/api/query2") @lru_cache(maxsize=1) def get_settings() -> Settings: return Settings()