"""Settings endpoints. The browser never sees a real token: `GET` masks every secret, and a `PUT` that sends the mask back untouched keeps the stored value. """ from __future__ import annotations import json from typing import Any from fastapi import APIRouter, Body, HTTPException from pydantic import ValidationError from app.api.deps import DbSession, Settings from app.services import blocker_service, settings_service from app.settings_schema import AppSettings router = APIRouter(prefix="/api/settings", tags=["settings"]) @router.get("") def get_settings(settings: Settings) -> dict: return { "settings": settings_service.mask_secrets(settings.model_dump(mode="json")), "defaults": { "blocked_domains": blocker_service.DEFAULT_DOMAINS, "blocked_apps": blocker_service.DEFAULT_APPS, }, } @router.put("") def put_settings(session: DbSession, patch: dict[str, Any] = Body(...)) -> dict: try: updated = settings_service.apply_patch(session, patch) except ValidationError as exc: # `exc.errors()` can carry exception objects in `ctx`, which JSON cannot # take; pydantic's own `json()` renders them as text. raise HTTPException(status_code=422, detail=json.loads(exc.json())) from exc return {"settings": settings_service.mask_secrets(updated.model_dump(mode="json"))} @router.post("/reset") def reset_settings(session: DbSession) -> dict: """Back to defaults, re-seeded from the environment.""" updated = settings_service.save_settings(session, settings_service.settings_from_env()) return {"settings": settings_service.mask_secrets(updated.model_dump(mode="json"))} @router.get("/schema") def settings_schema() -> dict: """The JSON Schema, so a client can render the settings form generically.""" return AppSettings.model_json_schema()