from typing import Optional from fastapi import APIRouter, Depends, HTTPException, Request, Security, status from fastapi.security import HTTPAuthorizationCredentials from pydantic import BaseModel, EmailStr from sqlalchemy.orm import Session from app.core.database import get_db from app.core.security import api_key_header, require_admin_auth, security_bearer from app.models.setting import Setting router = APIRouter() # Simple in-memory storage for setup status (for Milestone 1) setup_status = { "is_setup_complete": False, "admin_email": None, "app_name": "DMARQ", } SETUP_COMPLETE_KEY = "setup.is_complete" SETUP_ADMIN_EMAIL_KEY = "setup.admin_email" GENERAL_APP_NAME_KEY = "general.app_name" GENERAL_BASE_URL_KEY = "general.base_url" def _setting_value(db: Session, key: str) -> Optional[str]: row = db.query(Setting).filter(Setting.key == key).first() return row.value if row else None def _is_true(value: Optional[str]) -> bool: return str(value).strip().lower() in {"1", "true", "yes", "on"} def _upsert_setting( db: Session, key: str, value: Optional[str], *, description: str, value_type: str = "string", category: str = "setup", ) -> None: row = db.query(Setting).filter(Setting.key == key).first() if row is None: db.add( Setting( key=key, value=value, description=description, value_type=value_type, category=category, ) ) return row.value = value row.description = row.description or description row.value_type = row.value_type or value_type row.category = row.category or category def _refresh_setup_status_from_db(db: Session) -> dict: """Merge persisted setup state into the legacy in-memory setup status.""" persisted_complete = _is_true(_setting_value(db, SETUP_COMPLETE_KEY)) if persisted_complete: setup_status["is_setup_complete"] = True persisted_admin_email = _setting_value(db, SETUP_ADMIN_EMAIL_KEY) if persisted_admin_email: setup_status["admin_email"] = persisted_admin_email persisted_app_name = _setting_value(db, GENERAL_APP_NAME_KEY) if persisted_app_name: setup_status["app_name"] = persisted_app_name return setup_status def _setup_is_complete(db: Session) -> bool: return bool(setup_status["is_setup_complete"]) or _is_true( _setting_value(db, SETUP_COMPLETE_KEY) ) class SetupStatusResponse(BaseModel): """Setup status response""" is_setup_complete: bool app_name: str class AdminSetupRequest(BaseModel): """Admin user setup request body""" email: EmailStr username: str password: str class SystemConfigRequest(BaseModel): """System configuration setup request body""" app_name: str base_url: str async def require_setup_write_auth( request: Request, db: Session = Depends(get_db), api_key: Optional[str] = Security(api_key_header), bearer: Optional[HTTPAuthorizationCredentials] = Security(security_bearer), ) -> dict: """Allow unauthenticated first-time setup writes, then require admin auth.""" if not _setup_is_complete(db): return {"auth_type": "initial_setup"} return await require_admin_auth(request=request, api_key=api_key, bearer=bearer) @router.get("/status", response_model=SetupStatusResponse) async def get_setup_status(db: Session = Depends(get_db)): """Get the current setup status""" current_status = _refresh_setup_status_from_db(db) return SetupStatusResponse( is_setup_complete=current_status["is_setup_complete"], app_name=current_status["app_name"], ) @router.post("/admin", status_code=201) async def setup_admin( request: AdminSetupRequest, db: Session = Depends(get_db), _auth: dict = Depends(require_setup_write_auth), ): """ Setup admin user during initial system configuration. For Milestone 1, this simply stores the admin email in memory. """ if _setup_is_complete(db): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Setup already completed" ) # Store admin email setup_status["admin_email"] = request.email _upsert_setting( db, SETUP_ADMIN_EMAIL_KEY, request.email, description="Email address configured during initial setup", ) db.commit() return {"message": "Admin user setup completed"} @router.post("/system", status_code=200) async def setup_system( request: SystemConfigRequest, db: Session = Depends(get_db), _auth: dict = Depends(require_setup_write_auth), ): """ Setup system configuration. For Milestone 1, this simply stores the app name in memory. """ # Store app name setup_status["app_name"] = request.app_name setup_status["is_setup_complete"] = True _upsert_setting( db, GENERAL_APP_NAME_KEY, request.app_name, description="Application display name shown in the UI", category="general", ) _upsert_setting( db, GENERAL_BASE_URL_KEY, request.base_url, description="Public base URL for this DMARQ instance", category="general", ) _upsert_setting( db, SETUP_COMPLETE_KEY, "true", description="Whether initial setup has been completed", value_type="boolean", ) db.commit() return {"message": "System settings saved successfully"}