feat: production deployment - full update with providers, admin, glossaries, pricing, tests
Major changes across backend, frontend, infrastructure: - Provider system with model selection (Google, DeepL, OpenAI, Ollama, Google Cloud) - Admin panel: user management, pricing, settings - Glossary system with CSV import/export - Subscription and tier quota management - Security hardening (rate limiting, API key auth, path traversal fixes) - Docker compose for dev, prod, and IONOS deployment - Alembic migrations for new tables - Frontend: dashboard, pricing page, landing page, i18n (en/fr) - Test suite and verification scripts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
59
config.py
59
config.py
@@ -56,12 +56,17 @@ class Config:
|
||||
|
||||
# ============== Security ==============
|
||||
ENABLE_HSTS = os.getenv("ENABLE_HSTS", "false").lower() == "true"
|
||||
CORS_ORIGINS = [o.strip() for o in os.getenv("CORS_ORIGINS", "").split(",") if o.strip()]
|
||||
_CORS_ORIGINS_RAW = os.getenv("CORS_ORIGINS", "")
|
||||
CORS_ORIGINS = [o.strip() for o in _CORS_ORIGINS_RAW.split(",") if o.strip()]
|
||||
# Raw value for "*" / empty checks (single source of truth)
|
||||
CORS_ORIGINS_RAW = _CORS_ORIGINS_RAW
|
||||
MAX_REQUEST_SIZE_MB = int(os.getenv("MAX_REQUEST_SIZE_MB", "100"))
|
||||
REQUEST_TIMEOUT_SECONDS = int(os.getenv("REQUEST_TIMEOUT_SECONDS", "300"))
|
||||
|
||||
# ============== Monitoring ==============
|
||||
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
|
||||
LOG_FORMAT = os.getenv("LOG_FORMAT", "json")
|
||||
ENV = os.getenv("ENV", os.getenv("ENVIRONMENT", "development")).lower()
|
||||
ENABLE_REQUEST_LOGGING = (
|
||||
os.getenv("ENABLE_REQUEST_LOGGING", "true").lower() == "true"
|
||||
)
|
||||
@@ -96,5 +101,57 @@ All API endpoints are versioned under /api/v1/ prefix for backward compatibility
|
||||
cls.TEMP_DIR.mkdir(exist_ok=True, parents=True)
|
||||
cls.LOGS_DIR.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
@classmethod
|
||||
def validate_required_env(cls) -> list[str]:
|
||||
"""
|
||||
Validate required environment variables (Story 6.6 - NFR10).
|
||||
In production (ENV=production): returns list of missing required vars; app should exit if non-empty.
|
||||
In development: returns [] so defaults/warnings can be used (e.g. REDIS_URL optional if rate limit off).
|
||||
"""
|
||||
env = os.getenv("ENV", os.getenv("ENVIRONMENT", "development")).lower()
|
||||
if env != "production":
|
||||
return []
|
||||
missing: list[str] = []
|
||||
if not os.getenv("JWT_SECRET_KEY", "").strip():
|
||||
missing.append("JWT_SECRET_KEY")
|
||||
if not os.getenv("ADMIN_USERNAME", "").strip():
|
||||
missing.append("ADMIN_USERNAME")
|
||||
admin_pass = os.getenv("ADMIN_PASSWORD", "").strip()
|
||||
admin_hash = os.getenv("ADMIN_PASSWORD_HASH", "").strip()
|
||||
if not admin_pass and not admin_hash:
|
||||
missing.append("ADMIN_PASSWORD or ADMIN_PASSWORD_HASH")
|
||||
if not os.getenv("ADMIN_TOKEN_SECRET", "").strip():
|
||||
missing.append("ADMIN_TOKEN_SECRET")
|
||||
rate_limit_on = os.getenv("RATE_LIMIT_ENABLED", "true").lower() == "true"
|
||||
if rate_limit_on and not os.getenv("REDIS_URL", "").strip():
|
||||
missing.append("REDIS_URL")
|
||||
db_url = cls._get_database_url()
|
||||
if not db_url:
|
||||
missing.append("DATABASE_URL")
|
||||
return missing
|
||||
|
||||
@classmethod
|
||||
def _get_database_url(cls) -> str:
|
||||
"""Return DATABASE_URL or build from POSTGRES_* (AC #1 - Story 6.6)."""
|
||||
url = os.getenv("DATABASE_URL", "").strip()
|
||||
if url:
|
||||
return url
|
||||
host = os.getenv("POSTGRES_HOST", "").strip()
|
||||
port = os.getenv("POSTGRES_PORT", "5432").strip()
|
||||
user = os.getenv("POSTGRES_USER", "").strip()
|
||||
password = os.getenv("POSTGRES_PASSWORD", "").strip()
|
||||
db = os.getenv("POSTGRES_DB", "").strip()
|
||||
if host and user and db:
|
||||
from urllib.parse import quote_plus
|
||||
|
||||
pw = quote_plus(password) if password else ""
|
||||
return f"postgresql://{user}:{pw}@{host}:{port}/{db}"
|
||||
return ""
|
||||
|
||||
|
||||
config = Config()
|
||||
|
||||
# So that database/connection.py and alembic see DATABASE_URL when only POSTGRES_* is set (AC #1)
|
||||
_effective_db_url = Config._get_database_url()
|
||||
if _effective_db_url and not os.environ.get("DATABASE_URL", "").strip():
|
||||
os.environ["DATABASE_URL"] = _effective_db_url
|
||||
|
||||
Reference in New Issue
Block a user