A full-year Zoho time-log export can run 30-60 MB and was getting
rejected with {"detail":"Payload too large"} on the Department tab
upload. 20 MB was an under-cautious default; 100 MB matches what
Apache will pass through without a LimitRequestBody override.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
90 lines
2.9 KiB
Python
90 lines
2.9 KiB
Python
"""Application settings loaded from environment variables.
|
|
|
|
Decisions:
|
|
- Uses pydantic-settings v2 so values are validated at import-time. Missing
|
|
required fields raise at startup rather than producing a 500 later.
|
|
- All cache TTLs default to the values documented in the docker-compose file.
|
|
- AIRTABLE_PAT and SESSION_SECRET have empty defaults so that tests / local
|
|
dev can boot without them (auth bypass paths short-circuit the checks).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from functools import lru_cache
|
|
from typing import Literal
|
|
|
|
from pydantic import Field
|
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
|
|
|
|
class Settings(BaseSettings):
|
|
model_config = SettingsConfigDict(
|
|
env_file=None,
|
|
case_sensitive=True,
|
|
extra="ignore",
|
|
)
|
|
|
|
# Airtable
|
|
AIRTABLE_PAT: str = ""
|
|
AIRTABLE_BASE_ID: str = "appoByydxIQANKtSh"
|
|
# Table names — kept as constants here; could be promoted to env vars later.
|
|
AIRTABLE_TABLE_RESOURCES: str = "Resource"
|
|
AIRTABLE_TABLE_BOOKINGS: str = "Booking Resource"
|
|
|
|
# Session / auth
|
|
SESSION_SECRET: str = "dev-insecure-secret-change-me"
|
|
ADMIN_USERNAME: str = "admin"
|
|
ADMIN_PASSWORD_BCRYPT: str = ""
|
|
AUTH_MODE: Literal["local", "azure"] = "local"
|
|
DEV_AUTH_BYPASS: bool = False
|
|
|
|
# Cache TTLs (seconds)
|
|
CACHE_TTL_RESOURCES: int = 600
|
|
CACHE_TTL_BOOKINGS: int = 60
|
|
CACHE_TTL_META: int = 600
|
|
|
|
# Azure (stubbed in v1)
|
|
AZURE_TENANT_ID: str = ""
|
|
AZURE_CLIENT_ID: str = ""
|
|
|
|
# App
|
|
APP_BASE_PATH: str = "/utilisation-dept"
|
|
# Override cookie path explicitly when needed (tests use "/" to bypass
|
|
# the Apache-stripped-prefix mismatch).
|
|
SESSION_COOKIE_PATH: str = ""
|
|
|
|
# CORS — comma separated origins. Empty in prod (same-origin via Apache).
|
|
CORS_ALLOWED_ORIGINS: str = ""
|
|
|
|
# Session cookie
|
|
SESSION_MAX_AGE: int = 60 * 60 * 8 # 8h
|
|
|
|
# Multipart upload limit (bytes). Default 100 MB — full-year Zoho time-log
|
|
# exports can run 30-60 MB, and we want headroom. Override via env var.
|
|
MAX_UPLOAD_BYTES: int = 100 * 1024 * 1024
|
|
|
|
@property
|
|
def cookie_path(self) -> str:
|
|
# Allow an explicit override for tests / unusual deploys.
|
|
if self.SESSION_COOKIE_PATH:
|
|
return self.SESSION_COOKIE_PATH
|
|
# itsdangerous cookie path; needs trailing slash to match the SPA mount.
|
|
p = self.APP_BASE_PATH.rstrip("/")
|
|
return f"{p}/" if p else "/"
|
|
|
|
@property
|
|
def cors_origins(self) -> list[str]:
|
|
if not self.CORS_ALLOWED_ORIGINS:
|
|
# Dev convenience: when bypass is on, allow Vite default.
|
|
if self.DEV_AUTH_BYPASS:
|
|
return ["http://localhost:5173"]
|
|
return []
|
|
return [o.strip() for o in self.CORS_ALLOWED_ORIGINS.split(",") if o.strip()]
|
|
|
|
|
|
@lru_cache
|
|
def get_settings() -> Settings:
|
|
return Settings()
|
|
|
|
|
|
settings = get_settings()
|