"""Setup wizard router — unauthenticated endpoints for first-time setup.""" from __future__ import annotations import logging from typing import Any, Dict, List, Optional from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from server.auth import create_access_token, hash_password, require_admin from server.services import settings_service from server.services.test_connections import TEST_FUNCTIONS logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/setup", tags=["setup"]) # --------------------------------------------------------------------------- # Guards # --------------------------------------------------------------------------- async def _require_setup_incomplete() -> None: """Raise 403 if setup is already complete (admin user exists).""" user = await settings_service.get_admin_user() if user is not None: raise HTTPException(status_code=403, detail="Setup already complete") # --------------------------------------------------------------------------- # Models # --------------------------------------------------------------------------- class SetupStatusResponse(BaseModel): setup_complete: bool integrations: List[Dict[str, Any]] class CreateAdminRequest(BaseModel): password: str class CreateAdminResponse(BaseModel): token: str username: str class SetupIntegrationUpdate(BaseModel): config: Dict[str, Any] enabled: bool class TestConfigRequest(BaseModel): config: Dict[str, Any] # --------------------------------------------------------------------------- # Endpoints # --------------------------------------------------------------------------- @router.get("/status") async def get_setup_status() -> SetupStatusResponse: """Check whether first-time setup has been completed.""" user = await settings_service.get_admin_user() setup_complete = user is not None integrations: List[Dict[str, Any]] = [] if not setup_complete: try: raw = await settings_service.get_integrations() integrations = [ {"type": i["type"], "name": i["name"], "enabled": i["enabled"]} for i in raw ] except Exception: pass return SetupStatusResponse( setup_complete=setup_complete, integrations=integrations, ) @router.post("/admin") async def create_admin( body: CreateAdminRequest, _: None = Depends(_require_setup_incomplete), ) -> CreateAdminResponse: """Create the admin user (step 1 of wizard).""" if len(body.password) < 6: raise HTTPException( status_code=400, detail="Passwort muss mindestens 6 Zeichen haben", ) await settings_service.create_admin_user("admin", hash_password(body.password)) token = create_access_token("admin") logger.info("Setup wizard: admin user created") return CreateAdminResponse(token=token, username="admin") @router.put("/integration/{type_name}") async def setup_integration( type_name: str, body: SetupIntegrationUpdate, admin_user: str = Depends(require_admin), ) -> Dict[str, Any]: """Save integration config during setup.""" existing = await settings_service.get_integration(type_name) if existing is None: raise HTTPException(status_code=404, detail=f"Integration '{type_name}' not found") result = await settings_service.upsert_integration( type_name=type_name, name=existing["name"], config=body.config, enabled=body.enabled, display_order=existing["display_order"], ) return result @router.post("/integration/{type_name}/test") async def test_integration_config( type_name: str, body: TestConfigRequest, admin_user: str = Depends(require_admin), ) -> Dict[str, Any]: """Test an integration with unsaved config values.""" test_fn = TEST_FUNCTIONS.get(type_name) if test_fn is None: return {"success": False, "message": f"Kein Test für '{type_name}' verfügbar"} return await test_fn(body.config) @router.post("/complete") async def complete_setup( admin_user: str = Depends(require_admin), ) -> Dict[str, str]: """Finalize setup: reload settings and start services.""" from server.config import get_settings, reload_settings from server.services.mqtt_service import mqtt_service await reload_settings() cfg = get_settings() if cfg.mqtt_enabled and cfg.mqtt_host: try: await mqtt_service.start( host=cfg.mqtt_host, port=cfg.mqtt_port, username=cfg.mqtt_username or None, password=cfg.mqtt_password or None, topics=cfg.mqtt_topics, client_id=cfg.mqtt_client_id, ) logger.info("Setup wizard: MQTT started (%s:%d)", cfg.mqtt_host, cfg.mqtt_port) except Exception: logger.exception("Setup wizard: failed to start MQTT") logger.info("Setup wizard completed successfully") return {"status": "ok", "message": "Setup abgeschlossen"}