Files
headquarter/apps/api/src/config.py
T
Developer 81b9a66ef5 fix: resolve stale backend test imports and schema drift
- Delete 4 obsolete unit tests tied to removed git mount/clone models
- Update imports and assertions across unit/integration/service tests
- Fix Settings defaults (postgres host, JWT props, cookie_samesite)
- Add skip guards for PostgreSQL-dependent integration tests
- Fix GitService env assertions and HealthMonitor state-change tests
- Repair docker/container inspect assertions in test_docker_service
- Fix ToolTypeCreate default_port validator ordering bug
- Fix check_port_exposed substring false-positive for port 0
- Update test_tool_types_api_extended to use interface_type field

Quality gates: pytest 311 passed, 34 skipped; npm typecheck/lint/test 87 passed
2026-06-12 20:23:17 +00:00

149 lines
4.8 KiB
Python

from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
def build_database_url(
*,
user: str,
password: str,
host: str,
port: int,
database: str,
) -> str:
return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{database}"
class Settings(BaseSettings):
app_env: str = "development"
database_url_override: str | None = Field(default=None, alias="DATABASE_URL")
postgres_user: str = "headquarter"
postgres_password: str = "headquarter"
postgres_host: str = "postgres"
postgres_port: int = 5432
postgres_db: str = "headquarter"
# Domain configuration
api_domain: str = "localhost"
web_domain: str = "localhost"
authentik_domain: str = "authentik.local"
# Public URLs (constructed from domains if not explicitly set)
api_public_url: str | None = None
web_public_url: str | None = None
# Authentik configuration - no hardcoded URLs
authentik_client_id: str = "headquarter-web"
authentik_client_secret: str = "change-me"
# Authentik application slug used in URLs (e.g., "headquarter-web")
# This is different from the OAuth client_id which may be a UUID
authentik_application_slug: str = "headquarter-web"
authentik_authorize_url: str | None = None
authentik_token_url: str | None = None
authentik_jwks_url: str | None = None
authentik_issuer: str | None = None
authentik_audience: str = "headquarter-web"
# Session configuration
session_secret: str = "change-me-session-secret"
session_ttl_hours: int = 24
# Internal JWT configuration (used for service-to-service tokens)
jwt_algorithm: str = "HS256"
access_token_ttl_minutes: int = 15
refresh_token_ttl_days: int = 7
# Repository storage
repo_base_path: str = "/data/repos"
# Tool instance storage
instance_base_path: str = "/data/instances"
model_config = SettingsConfigDict(env_file=".env", extra="ignore", populate_by_name=True)
@property
def database_url(self) -> str:
if self.database_url_override:
return self.database_url_override
return build_database_url(
user=self.postgres_user,
password=self.postgres_password,
host=self.postgres_host,
port=self.postgres_port,
database=self.postgres_db,
)
@property
def api_base_url(self) -> str:
if self.api_public_url:
return self.api_public_url
protocol = "https" if self.app_env == "production" else "http"
port = "" if self.app_env == "production" else ":8000"
return f"{protocol}://{self.api_domain}{port}"
@property
def web_base_url(self) -> str:
if self.web_public_url:
return self.web_public_url
protocol = "https" if self.app_env == "production" else "http"
port = "" if self.app_env == "production" else ":3000"
return f"{protocol}://{self.web_domain}{port}"
@property
def authentik_base_url(self) -> str:
protocol = "https" if self.app_env == "production" else "http"
return f"{protocol}://{self.authentik_domain}"
@property
def resolved_authentik_authorize_url(self) -> str:
if self.authentik_authorize_url:
return self.authentik_authorize_url
return f"{self.authentik_base_url}/application/o/authorize/"
@property
def resolved_authentik_token_url(self) -> str:
if self.authentik_token_url:
return self.authentik_token_url
return f"{self.authentik_base_url}/application/o/token/"
@property
def resolved_authentik_jwks_url(self) -> str:
if self.authentik_jwks_url:
return self.authentik_jwks_url
return f"{self.authentik_base_url}/application/o/{self.authentik_application_slug}/jwks/"
@property
def resolved_authentik_issuer(self) -> str:
if self.authentik_issuer:
return self.authentik_issuer
return f"{self.authentik_base_url}/application/o/{self.authentik_application_slug}/"
@property
def cookie_secure(self) -> bool:
return self.app_env == "production"
@property
def cookie_samesite(self) -> str:
if self.app_env == "production":
return "strict"
return "lax"
@property
def cookie_domain(self) -> str | None:
"""Return the parent domain for cross-subdomain cookies.
E.g., api.example.com and app.example.com both share .example.com
"""
if self.app_env != "production":
return None
# Extract parent domain from api_domain
# e.g., "api.headquarter.commumedia.org" -> ".headquarter.commumedia.org"
parts = self.api_domain.split(".")
if len(parts) >= 3:
return "." + ".".join(parts[1:])
return None