This commit enforces the repository boundary by eliminating direct database connection dependencies (DbDep) from all routers. Routers now depend on service context dependencies that combine the database connection with the related repositories. Changes: - Add 5 service context dependencies in dependencies.py: * SessionServiceContext: db + session_repo * BlocklistServiceContext: db + blocklist_repo + import_log_repo + settings_repo * SettingsServiceContext: db + settings_repo * BanServiceContext: db + fail2ban_db_repo * HistoryServiceContext: db + fail2ban_db_repo + history_archive_repo - Refactor all 9 routers (auth, bans, blocklist, config_misc, dashboard, geo, history, jails, setup) to use service contexts instead of DbDep. - Update Backend-Development.md with clear examples of the new pattern and documentation of available service contexts. Rationale: - Enforces the repository boundary through the dependency system - Makes database operations explicit and auditable - Improves testability by allowing service contexts to be mocked - Prevents accidental direct database access from routers The deprecated DbDep remains available for backward compatibility with services that have not yet been refactored, but routers can no longer import it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
106 lines
3.4 KiB
Python
106 lines
3.4 KiB
Python
"""Setup router.
|
|
|
|
Exposes the ``POST /api/setup`` endpoint for the one-time first-run
|
|
configuration wizard. Once setup has been completed, subsequent calls
|
|
return ``409 Conflict``.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import structlog
|
|
from fastapi import APIRouter, HTTPException, status
|
|
|
|
from app.dependencies import AppDep, SettingsDep, SettingsServiceContextDep
|
|
from app.models.setup import SetupRequest, SetupResponse, SetupStatusResponse, SetupTimezoneResponse
|
|
from app.services import setup_service
|
|
from app.utils.runtime_state import update_app_settings
|
|
from app.utils.setup_state import is_setup_complete_cached, set_setup_complete_cache
|
|
|
|
log: structlog.stdlib.BoundLogger = structlog.get_logger()
|
|
|
|
router = APIRouter(prefix="/api/setup", tags=["setup"])
|
|
|
|
|
|
@router.get(
|
|
"",
|
|
response_model=SetupStatusResponse,
|
|
summary="Check whether setup has been completed",
|
|
)
|
|
async def get_setup_status(app: AppDep) -> SetupStatusResponse:
|
|
"""Return whether the initial setup wizard has been completed.
|
|
|
|
Returns:
|
|
:class:`~app.models.setup.SetupStatusResponse` with ``completed``
|
|
set to ``True`` if setup is done, ``False`` otherwise.
|
|
"""
|
|
done = is_setup_complete_cached(app)
|
|
return SetupStatusResponse(completed=done)
|
|
|
|
|
|
@router.post(
|
|
"",
|
|
response_model=SetupResponse,
|
|
status_code=status.HTTP_201_CREATED,
|
|
summary="Run the initial setup wizard",
|
|
)
|
|
async def post_setup(
|
|
app: AppDep,
|
|
body: SetupRequest,
|
|
settings_ctx: SettingsServiceContextDep,
|
|
) -> SetupResponse:
|
|
"""Persist the initial BanGUI configuration.
|
|
|
|
Args:
|
|
app: The FastAPI application instance.
|
|
body: Setup request payload validated by Pydantic.
|
|
settings_ctx: Settings service context containing db and repository.
|
|
|
|
Returns:
|
|
:class:`~app.models.setup.SetupResponse` on success.
|
|
|
|
Raises:
|
|
HTTPException: 409 if setup has already been completed.
|
|
"""
|
|
if is_setup_complete_cached(app) or await setup_service.is_setup_complete(settings_ctx.db):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_409_CONFLICT,
|
|
detail="Setup has already been completed.",
|
|
)
|
|
|
|
await setup_service.run_setup(
|
|
settings_ctx.db,
|
|
master_password=body.master_password,
|
|
database_path=body.database_path,
|
|
fail2ban_socket=body.fail2ban_socket,
|
|
timezone=body.timezone,
|
|
session_duration_minutes=body.session_duration_minutes,
|
|
)
|
|
set_setup_complete_cache(app, True)
|
|
update_app_settings(
|
|
app,
|
|
database_path=body.database_path,
|
|
fail2ban_socket=body.fail2ban_socket,
|
|
timezone=body.timezone,
|
|
session_duration_minutes=body.session_duration_minutes,
|
|
)
|
|
return SetupResponse()
|
|
|
|
|
|
@router.get(
|
|
"/timezone",
|
|
response_model=SetupTimezoneResponse,
|
|
summary="Return the configured IANA timezone",
|
|
)
|
|
async def get_timezone(settings: SettingsDep) -> SetupTimezoneResponse:
|
|
"""Return the IANA timezone configured during the initial setup wizard.
|
|
|
|
The frontend uses this to convert UTC timestamps to the local time zone
|
|
chosen by the administrator.
|
|
|
|
Returns:
|
|
:class:`~app.models.setup.SetupTimezoneResponse` with ``timezone``
|
|
set to the stored IANA identifier (e.g. ``"UTC"`` or
|
|
``"Europe/Berlin"``), defaulting to ``"UTC"`` if unset.
|
|
"""
|
|
return SetupTimezoneResponse(timezone=settings.timezone)
|