cuatrolabs-spa-ms / app /core /config.py
Michael-Antony's picture
feat: Add booking lifecycle APIs, MinIO storage, and OTP generation
090f75a
"""
Configuration settings for Spa microservice.
Loads environment variables and provides application settings.
"""
import os
from typing import Optional, List
from pydantic import model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
"""Application settings loaded from environment variables"""
# Application
APP_NAME: str = "Spa Microservice"
APP_VERSION: str = "1.0.0"
DEBUG: bool = False
# MongoDB Configuration
MONGODB_URI: str = os.getenv("MONGODB_URI", "mongodb://localhost:27017")
MONGODB_DB_NAME: str = os.getenv("MONGODB_DB_NAME", "cuatrolabs")
# PostgreSQL Configuration (for trans schema sync)
POSTGRES_HOST: str = os.getenv("DB_HOST", "ep-sweet-surf-a1qeduoy.ap-southeast-1.aws.neon.tech")
POSTGRES_PORT: int = int(os.getenv("DB_PORT", "5432"))
POSTGRES_DB: str = os.getenv("DB_NAME", "cuatrolabs")
POSTGRES_USER: str = os.getenv("DB_USER", "trans_owner")
POSTGRES_PASSWORD: str = os.getenv("DB_PASSWORD", "BookMyService7")
POSTGRES_MIN_POOL_SIZE: int = int(os.getenv("POSTGRES_MIN_POOL_SIZE", "5"))
POSTGRES_MAX_POOL_SIZE: int = int(os.getenv("POSTGRES_MAX_POOL_SIZE", "20"))
POSTGRES_CONNECT_MAX_RETRIES: int = int(os.getenv("POSTGRES_CONNECT_MAX_RETRIES", "20"))
POSTGRES_CONNECT_INITIAL_DELAY_MS: int = int(os.getenv("POSTGRES_CONNECT_INITIAL_DELAY_MS", "500"))
POSTGRES_CONNECT_BACKOFF_MULTIPLIER: float = float(os.getenv("POSTGRES_CONNECT_BACKOFF_MULTIPLIER", "1.5"))
POSTGRES_SSL_MODE: str = os.getenv("DB_SSLMODE", "disable")
POSTGRES_SSL_ROOT_CERT: Optional[str] = os.getenv("POSTGRES_SSL_ROOT_CERT")
POSTGRES_SSL_CERT: Optional[str] = os.getenv("POSTGRES_SSL_CERT")
POSTGRES_SSL_KEY: Optional[str] = os.getenv("POSTGRES_SSL_KEY")
POSTGRES_URI: Optional[str] = None
# MinIO / Object Storage
MINIO_ENDPOINT: str = os.getenv("MINIO_ENDPOINT", "localhost:9000")
MINIO_ACCESS_KEY: Optional[str] = os.getenv("MINIO_ACCESS_KEY")
MINIO_SECRET_KEY: Optional[str] = os.getenv("MINIO_SECRET_KEY")
MINIO_SECURE: bool = os.getenv("MINIO_SECURE", "false").lower() == "true"
MINIO_REGION: Optional[str] = os.getenv("MINIO_REGION")
# Storage buckets
STORAGE_BUCKET_IMAGES_PUBLIC: str = os.getenv("STORAGE_BUCKET_IMAGES_PUBLIC", "cutra-spa-images-public")
STORAGE_BUCKET_IMAGES_PRIVATE: str = os.getenv("STORAGE_BUCKET_IMAGES_PRIVATE", "cutra-spa-images")
STORAGE_MAX_IMAGE_MB: int = 10
STORAGE_PRESIGN_EXPIRY_SECONDS: int = 900
STORAGE_IMAGE_CDN_EXPIRY_SECONDS: int = 604800
# Public URL / CDN configuration
STORAGE_PUBLIC_BASE_URL: Optional[str] = os.getenv("STORAGE_PUBLIC_BASE_URL")
STORAGE_ENABLE_PUBLIC_URLS: bool = os.getenv("STORAGE_ENABLE_PUBLIC_URLS", "true").lower() == "true"
@property
def PUBLIC_BASE_URL(self) -> str:
"""Get the public base URL for images, preferring CDN over direct MinIO endpoint."""
if self.STORAGE_PUBLIC_BASE_URL:
return self.STORAGE_PUBLIC_BASE_URL.rstrip("/")
protocol = "https" if self.MINIO_SECURE else "http"
return f"{protocol}://{self.MINIO_ENDPOINT}"
@property
def IS_CDN_ENABLED(self) -> bool:
"""Check if CDN is configured (not using direct MinIO endpoint)."""
return bool(self.STORAGE_PUBLIC_BASE_URL)
# Redis Configuration
REDIS_HOST: str = os.getenv("REDIS_HOST", "localhost")
REDIS_PORT: int = int(os.getenv("REDIS_PORT", "6379"))
REDIS_PASSWORD: Optional[str] = os.getenv("REDIS_PASSWORD")
REDIS_DB: int = int(os.getenv("REDIS_DB", "0"))
# JWT Configuration
SECRET_KEY: str = os.getenv("SECRET_KEY", "your-secret-key-change-in-production")
ALGORITHM: str = os.getenv("ALGORITHM", "HS256")
TOKEN_EXPIRATION_HOURS: int = int(os.getenv("TOKEN_EXPIRATION_HOURS", "8"))
# OTP Configuration
OTP_TTL_SECONDS: int = int(os.getenv("OTP_TTL_SECONDS", "600"))
OTP_RATE_LIMIT_MAX: int = int(os.getenv("OTP_RATE_LIMIT_MAX", "10"))
OTP_RATE_LIMIT_WINDOW: int = int(os.getenv("OTP_RATE_LIMIT_WINDOW", "600"))
# Twilio Configuration
TWILIO_ACCOUNT_SID: Optional[str] = os.getenv("TWILIO_ACCOUNT_SID")
TWILIO_AUTH_TOKEN: Optional[str] = os.getenv("TWILIO_AUTH_TOKEN")
TWILIO_PHONE_NUMBER: Optional[str] = os.getenv("TWILIO_PHONE_NUMBER")
# SMTP Configuration
SMTP_HOST: Optional[str] = os.getenv("SMTP_HOST")
SMTP_PORT: int = int(os.getenv("SMTP_PORT", "587"))
SMTP_USERNAME: Optional[str] = os.getenv("SMTP_USERNAME")
SMTP_PASSWORD: Optional[str] = os.getenv("SMTP_PASSWORD")
SMTP_FROM_EMAIL: Optional[str] = os.getenv("SMTP_FROM_EMAIL")
SMTP_USE_TLS: bool = os.getenv("SMTP_USE_TLS", "true").lower() == "true"
# Logging
LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
# Dashboard Cache Configuration
DASHBOARD_REDIS_TTL_SECONDS: int = int(os.getenv("DASHBOARD_REDIS_TTL_SECONDS", "60")) # 60 seconds
DASHBOARD_MONGO_TTL_HOURS: int = int(os.getenv("DASHBOARD_MONGO_TTL_HOURS", "3")) # 3 hours (optional)
DASHBOARD_ENABLE_MONGO_TTL: bool = os.getenv("DASHBOARD_ENABLE_MONGO_TTL", "false").lower() == "true"
# CORS
CORS_ORIGINS: List[str] = [
"http://localhost:3000",
"http://localhost:8000",
]
# Pydantic v2 config
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=True,
extra="allow", # allows extra environment variables without error
)
@model_validator(mode='after')
def assemble_db_connection(self) -> 'Settings':
from urllib.parse import quote_plus
# Prefer DATABASE_URL and DATABASE_URI like TMS settings.py
env_url = (os.getenv("DATABASE_URL") or os.getenv("DATABASE_URI") or "").strip()
if env_url:
self.POSTGRES_URI = env_url
print(f"[CONFIG] Using provided DATABASE_URL/URI")
return self
# Build DSN from individual parts
if all([self.POSTGRES_USER, self.POSTGRES_PASSWORD, self.POSTGRES_HOST, self.POSTGRES_DB]):
protocol = os.getenv("DB_PROTOCOL", "postgresql+asyncpg")
# Ensure no spaces in connection components
user = self.POSTGRES_USER.strip()
host = self.POSTGRES_HOST.strip()
port = str(self.POSTGRES_PORT).strip()
db = self.POSTGRES_DB.strip()
self.POSTGRES_URI = f"{protocol}://{user}:{quote_plus(self.POSTGRES_PASSWORD)}@{host}:{port}/{db}"
print(f"[CONFIG] Built POSTGRES_URI from components")
print(f"[CONFIG] Protocol: {protocol}")
print(f"[CONFIG] User: {self.POSTGRES_USER}")
print(f"[CONFIG] Host: {self.POSTGRES_HOST}")
print(f"[CONFIG] Port: {self.POSTGRES_PORT}")
print(f"[CONFIG] Database: {self.POSTGRES_DB}")
print(f"[CONFIG] Password: {'SET' if self.POSTGRES_PASSWORD else 'EMPTY'}")
print(f"[CONFIG] SSL Mode: {self.POSTGRES_SSL_MODE}")
else:
self.POSTGRES_URI = None
print(f"[CONFIG] ERROR: Cannot build POSTGRES_URI - missing required components")
print(f"[CONFIG] POSTGRES_USER: {'SET' if self.POSTGRES_USER else 'MISSING'}")
print(f"[CONFIG] POSTGRES_PASSWORD: {'SET' if self.POSTGRES_PASSWORD else 'MISSING'}")
print(f"[CONFIG] POSTGRES_HOST: {'SET' if self.POSTGRES_HOST else 'MISSING'}")
print(f"[CONFIG] POSTGRES_DB: {'SET' if self.POSTGRES_DB else 'MISSING'}")
print(f"[CONFIG] Checking alternative env vars:")
print(f"[CONFIG] DB_USER: {os.getenv('DB_USER', 'NOT SET')}")
print(f"[CONFIG] DB_PASSWORD: {'SET' if os.getenv('DB_PASSWORD') else 'NOT SET'}")
print(f"[CONFIG] DB_HOST: {os.getenv('DB_HOST', 'NOT SET')}")
print(f"[CONFIG] DB_NAME: {os.getenv('DB_NAME', 'NOT SET')}")
return self
# Global settings instance
settings = Settings()