# /opt/docker/dev/service_finder/backend/app/core/config.py import os from pathlib import Path from typing import Any, Optional, List from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic import Field, field_validator from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncSession from datetime import datetime, timezone class Settings(BaseSettings): # --- Paths --- BASE_DIR: Path = Path(__file__).resolve().parent.parent.parent STATIC_DIR: str = os.path.join(str(BASE_DIR), "static") # --- General --- PROJECT_NAME: str = "Service Finder Ecosystem" VERSION: str = "2.1.0" API_V1_STR: str = "/api/v1" DEBUG: bool = False def get_now_utc_iso(self) -> str: """Központi időlekérdező az egész Sentinel rendszernek""" return datetime.now(timezone.utc).isoformat() # MB 2.0 Kompatibilitási alias a database.py számára @property def DEBUG_MODE(self) -> bool: return self.DEBUG # --- Security / JWT --- SECRET_KEY: str = "NOT_SET_DANGER" ALGORITHM: str = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 REFRESH_TOKEN_EXPIRE_DAYS: int = 7 @field_validator('SECRET_KEY') @classmethod def validate_secret_key(cls, v: str, info) -> str: """Ellenőrzi, hogy a SECRET_KEY ne legyen default érték éles környezetben.""" if v == "NOT_SET_DANGER" and not info.data.get("DEBUG", True): raise ValueError( "SECRET_KEY must be set in production environment. " "Please set SECRET_KEY in .env file." ) if not v or v.strip() == "": raise ValueError("SECRET_KEY cannot be empty.") return v # --- Initial Admin --- INITIAL_ADMIN_EMAIL: str = "admin@servicefinder.hu" INITIAL_ADMIN_PASSWORD: str = "Admin123!" # --- Database & Cache --- # Alapértelmezett értéket adunk, hogy ne szálljon el, ha a .env hiányos DATABASE_URL: str = Field( default="postgresql+asyncpg://user:password@postgres-db:5432/service_finder", env="DATABASE_URL" ) REDIS_URL: str = "redis://service_finder_redis:6379/0" @property def SQLALCHEMY_DATABASE_URI(self) -> str: """ Ez a property biztosítja, hogy a database.py és az Alembic megtalálja a kapcsolatot a várt néven. """ return self.DATABASE_URL # --- Email --- EMAIL_PROVIDER: str = "auto" EMAILS_FROM_EMAIL: str = "info@profibot.hu" EMAILS_FROM_NAME: str = "Profibot" SENDGRID_API_KEY: Optional[str] = None SMTP_HOST: Optional[str] = None SMTP_PORT: int = 587 SMTP_USER: Optional[str] = None SMTP_PASSWORD: Optional[str] = None # --- External URLs --- FRONTEND_BASE_URL: str = "https://dev.profibot.hu" BACKEND_CORS_ORIGINS: List[str] = Field( default=[ "http://localhost:3001", "https://dev.profibot.hu" ], description="Comma-separated list of allowed CORS origins. Set via ALLOWED_ORIGINS environment variable." ) @field_validator('BACKEND_CORS_ORIGINS', mode='before') @classmethod def parse_allowed_origins(cls, v: Any) -> List[str]: """Parse ALLOWED_ORIGINS environment variable from comma-separated string to list.""" import os env_val = os.getenv('ALLOWED_ORIGINS') if env_val: # parse environment variable env_val = env_val.strip() if env_val.startswith('"') and env_val.endswith('"'): env_val = env_val[1:-1] if env_val.startswith("'") and env_val.endswith("'"): env_val = env_val[1:-1] parts = [part.strip() for part in env_val.split(',') if part.strip()] return parts # if no env variable, fallback to default or provided value if isinstance(v, str): v = v.strip() if v.startswith('"') and v.endswith('"'): v = v[1:-1] if v.startswith("'") and v.endswith("'"): v = v[1:-1] parts = [part.strip() for part in v.split(',') if part.strip()] return parts return v # --- Google OAuth --- GOOGLE_CLIENT_ID: str = "" GOOGLE_CLIENT_SECRET: str = "" GOOGLE_CALLBACK_URL: str = "https://dev.profibot.hu/api/v1/auth/callback/google" # --- Brute-Force & Security --- LOGIN_RATE_LIMIT_ANON: str = "5/minute" AUTH_MIN_PASSWORD_LENGTH: int = 8 # --- Dinamikus Admin Motor (Sértetlenül hagyva) --- async def get_db_setting(self, db: AsyncSession, key_name: str, default: Any = None) -> Any: try: query = text("SELECT value FROM system.system_parameters WHERE key = :key") result = await db.execute(query, {"key": key_name}) row = result.fetchone() if row and row[0] is not None: return row[0] return default except Exception: return default model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", case_sensitive=True, extra="ignore" ) settings = Settings()