Files
maskanx_crm_frontend/app/core/config.py
T
AFFAANhandClaude Opus 5 f6a54d6f93 Initial commit: Maskan CRM backend
Independent FastAPI backend for Maskan CRM.

Owns contacts, organizations, leads, pipelines, activities, products,
quotes, users, permissions, audit records and first-party integration
credentials, with Alembic migrations against PostgreSQL.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 10:28:41 +05:30

108 lines
3.6 KiB
Python

from functools import lru_cache
from urllib.parse import quote_plus
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
extra="ignore",
)
app_name: str = "Maskan CRM"
environment: str = Field(default="development", alias="MASKAN_CRM_ENV")
mode: str = Field(default="standalone", alias="MASKAN_CRM_MODE")
cors_origins_raw: str = Field(
default="http://127.0.0.1:5174,http://localhost:5174",
alias="MASKAN_CRM_CORS_ORIGINS",
)
jwt_secret: str = Field(
default="development-only-change-me-at-least-32-characters",
alias="MASKAN_CRM_JWT_SECRET",
)
access_token_minutes: int = Field(
default=30,
alias="MASKAN_CRM_ACCESS_TOKEN_MINUTES",
)
jwt_issuer: str = "maskan-crm"
jwt_audience: str = "maskan-crm-api"
database_url_override: str | None = Field(default=None, alias="DATABASE_URL")
db_host: str = Field(default="127.0.0.1", alias="DB_HOST")
db_port: int = Field(default=5433, alias="DB_PORT")
db_name: str = Field(default="maskan_crm", alias="DB_NAME")
db_user: str = Field(default="postgres", alias="DB_USER")
db_password: str = Field(default="postgres", alias="DB_PASSWORD")
db_sslmode: str = Field(default="disable", alias="DB_SSLMODE")
bootstrap_workspace: str = Field(
default="maskan",
alias="MASKAN_CRM_BOOTSTRAP_WORKSPACE",
)
bootstrap_company: str = Field(
default="Maskan Technologies",
alias="MASKAN_CRM_BOOTSTRAP_COMPANY",
)
bootstrap_admin_email: str = Field(
default="owner@example.com",
alias="MASKAN_CRM_BOOTSTRAP_ADMIN_EMAIL",
)
bootstrap_admin_password: str = Field(
default="change-this-before-first-run",
alias="MASKAN_CRM_BOOTSTRAP_ADMIN_PASSWORD",
)
seed_demo: bool = Field(default=True, alias="MASKAN_CRM_SEED_DEMO")
@property
def cors_origins(self) -> list[str]:
return [item.strip() for item in self.cors_origins_raw.split(",") if item.strip()]
@property
def database_url(self) -> str:
if self.database_url_override:
return self.database_url_override
user = quote_plus(self.db_user)
password = quote_plus(self.db_password)
return (
f"postgresql+psycopg://{user}:{password}@{self.db_host}:"
f"{self.db_port}/{self.db_name}?sslmode={self.db_sslmode}"
)
def validate_production(self) -> None:
if self.environment.lower() != "production":
self.validate_database_backend()
return
if self.jwt_secret in {
"development-only-change-me-at-least-32-characters",
"replace-me",
}:
raise RuntimeError("MASKAN_CRM_JWT_SECRET must be set for production.")
if len(self.jwt_secret) < 32:
raise RuntimeError("MASKAN_CRM_JWT_SECRET must contain at least 32 characters.")
self.validate_database_backend()
def validate_database_backend(self) -> None:
"""Allow SQLite only in explicit automated-test environments."""
if not self.database_url.lower().startswith("sqlite"):
return
if self.environment.lower() not in {"test", "testing"}:
raise RuntimeError(
"Maskan CRM requires PostgreSQL outside automated tests. "
"Set DB_HOST/DB_PORT/DB_NAME/DB_USER/DB_PASSWORD or DATABASE_URL.",
)
@lru_cache
def get_settings() -> Settings:
settings = Settings()
settings.validate_production()
return settings