Refactor router dependency wiring to explicit app state providers

This commit is contained in:
2026-04-06 20:12:04 +02:00
parent f0ee466603
commit 594f55d157
5 changed files with 45 additions and 59 deletions

View File

@@ -16,6 +16,8 @@ from fastapi import Depends, HTTPException, Request, status
from app.config import Settings
from app.models.auth import Session
from app.models.config import PendingRecovery
from app.models.server import ServerStatus
from app.utils.time_utils import utc_now
import aiohttp
@@ -171,6 +173,15 @@ async def get_fail2ban_start_command(settings: Settings = Depends(get_settings))
"""Provide the configured fail2ban start command."""
return settings.fail2ban_start_command
async def get_server_status(request: Request) -> ServerStatus:
"""Return the cached fail2ban server status snapshot from app state."""
state = cast("AppState", request.app.state)
return getattr(state, "server_status", ServerStatus(online=False))
async def get_pending_recovery(request: Request) -> PendingRecovery | None:
"""Return the current pending recovery record from app state."""
state = cast("AppState", request.app.state)
return getattr(state, "pending_recovery", None)
async def require_auth(
request: Request,
@@ -242,4 +253,6 @@ SchedulerDep = Annotated[AsyncIOScheduler, Depends(get_scheduler)]
Fail2BanSocketDep = Annotated[str, Depends(get_fail2ban_socket)]
Fail2BanConfigDirDep = Annotated[str, Depends(get_fail2ban_config_dir)]
Fail2BanStartCommandDep = Annotated[str, Depends(get_fail2ban_start_command)]
ServerStatusDep = Annotated[ServerStatus, Depends(get_server_status)]
PendingRecoveryDep = Annotated[PendingRecovery | None, Depends(get_pending_recovery)]
AuthDep = Annotated[Session, Depends(require_auth)]

View File

@@ -121,16 +121,15 @@ async def create_blocklist(
summary="Trigger a manual blocklist import",
)
async def run_import_now(
request: Request,
http_session: HttpSessionDep,
db: DbDep,
_auth: AuthDep,
http_session: HttpSessionDep,
socket_path: Fail2BanSocketDep,
) -> ImportRunResult:
"""Download and apply all enabled blocklist sources immediately.
Args:
request: Incoming request (used to access shared HTTP session).
http_session: Shared HTTP session (injected).
db: Application database connection (injected).
_auth: Validated session — enforces authentication.
@@ -155,7 +154,6 @@ async def run_import_now(
summary="Get the current import schedule",
)
async def get_schedule(
request: Request,
db: DbDep,
_auth: AuthDep,
scheduler: SchedulerDep,
@@ -188,10 +186,10 @@ async def get_schedule(
)
async def update_schedule(
payload: ScheduleConfig,
request: Request,
db: DbDep,
_auth: AuthDep,
scheduler: SchedulerDep,
request: Request,
) -> ScheduleInfo:
"""Persist a new schedule configuration and reschedule the import job.
@@ -342,7 +340,7 @@ async def delete_blocklist(
)
async def preview_blocklist(
source_id: int,
request: Request,
http_session: HttpSessionDep,
db: DbDep,
_auth: AuthDep,
) -> PreviewResponse:
@@ -365,7 +363,6 @@ async def preview_blocklist(
if source is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Blocklist source not found.")
http_session: aiohttp.ClientSession = request.app.state.http_session
try:
return await blocklist_service.preview_source(source.url, http_session)
except ValueError as exc:

View File

@@ -14,7 +14,7 @@ from __future__ import annotations
from typing import Literal
from fastapi import APIRouter, Query, Request
from fastapi import APIRouter, Query
from app import __version__
from app.dependencies import (
@@ -22,6 +22,7 @@ from app.dependencies import (
DbDep,
Fail2BanSocketDep,
HttpSessionDep,
ServerStatusDep,
)
from app.models.ban import (
BanOrigin,
@@ -50,7 +51,7 @@ _DEFAULT_RANGE: TimeRange = "24h"
summary="Return the cached fail2ban server status",
)
async def get_server_status(
request: Request,
server_status: ServerStatusDep,
_auth: AuthDep,
) -> ServerStatusResponse:
"""Return the most recent fail2ban health snapshot.
@@ -60,18 +61,14 @@ async def get_server_status(
returned so the response is always well-formed.
Args:
request: The incoming request (used to access ``app.state``).
server_status: Cached fail2ban server health snapshot (injected).
_auth: Validated session — enforces authentication on this endpoint.
Returns:
:class:`~app.models.server.ServerStatusResponse` containing the
current health snapshot.
"""
cached: ServerStatus = getattr(
request.app.state,
"server_status",
ServerStatus(online=False),
)
cached: ServerStatus = server_status
cached.version = __version__
return ServerStatusResponse(status=cached)

View File

@@ -31,9 +31,9 @@ from __future__ import annotations
from typing import Annotated
from fastapi import APIRouter, HTTPException, Path, Request, status
from fastapi import APIRouter, HTTPException, Path, status
from app.dependencies import AuthDep
from app.dependencies import AuthDep, Fail2BanConfigDirDep
from app.models.config import (
ActionConfig,
ActionConfigUpdate,
@@ -117,7 +117,7 @@ def _service_unavailable(message: str) -> HTTPException:
summary="List all jail config files",
)
async def list_jail_config_files(
request: Request,
config_dir: Fail2BanConfigDirDep,
_auth: AuthDep,
) -> JailConfigFilesResponse:
"""Return metadata for every ``.conf`` and ``.local`` file in ``jail.d/``.
@@ -126,13 +126,12 @@ async def list_jail_config_files(
file (defaulting to ``true`` when the key is absent).
Args:
request: Incoming request (used for ``app.state.settings``).
config_dir: Config directory path injected from application settings.
_auth: Validated session — enforces authentication.
Returns:
:class:`~app.models.file_config.JailConfigFilesResponse`.
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
return await raw_config_io_service.list_jail_config_files(config_dir)
except ConfigDirError as exc:
@@ -145,7 +144,7 @@ async def list_jail_config_files(
summary="Return a single jail config file with its content",
)
async def get_jail_config_file(
request: Request,
config_dir: Fail2BanConfigDirDep,
_auth: AuthDep,
filename: _FilenamePath,
) -> JailConfigFileContent:
@@ -164,7 +163,6 @@ async def get_jail_config_file(
HTTPException: 404 if the file does not exist.
HTTPException: 503 if the config directory is unavailable.
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
return await raw_config_io_service.get_jail_config_file(config_dir, filename)
except ConfigFileNameError as exc:
@@ -181,7 +179,7 @@ async def get_jail_config_file(
summary="Overwrite a jail.d config file with new raw content",
)
async def write_jail_config_file(
request: Request,
config_dir: Fail2BanConfigDirDep,
_auth: AuthDep,
filename: _FilenamePath,
body: ConfFileUpdateRequest,
@@ -202,7 +200,6 @@ async def write_jail_config_file(
HTTPException: 404 if the file does not exist.
HTTPException: 503 if the config directory is unavailable.
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
await raw_config_io_service.write_jail_config_file(config_dir, filename, body)
except ConfigFileNameError as exc:
@@ -221,7 +218,7 @@ async def write_jail_config_file(
summary="Enable or disable a jail configuration file",
)
async def set_jail_config_file_enabled(
request: Request,
config_dir: Fail2BanConfigDirDep,
_auth: AuthDep,
filename: _FilenamePath,
body: JailConfigFileEnabledUpdate,
@@ -242,7 +239,6 @@ async def set_jail_config_file_enabled(
HTTPException: 404 if the file does not exist.
HTTPException: 503 if the config directory is unavailable.
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
await raw_config_io_service.set_jail_config_enabled(
config_dir, filename, body.enabled
@@ -264,7 +260,7 @@ async def set_jail_config_file_enabled(
summary="Create a new jail.d config file",
)
async def create_jail_config_file(
request: Request,
config_dir: Fail2BanConfigDirDep,
_auth: AuthDep,
body: ConfFileCreateRequest,
) -> ConfFileContent:
@@ -283,7 +279,6 @@ async def create_jail_config_file(
HTTPException: 409 if a file with that name already exists.
HTTPException: 503 if the config directory is unavailable.
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
filename = await raw_config_io_service.create_jail_config_file(config_dir, body)
except ConfigFileNameError as exc:
@@ -313,7 +308,7 @@ async def create_jail_config_file(
summary="Return a filter definition file's raw content",
)
async def get_filter_file_raw(
request: Request,
config_dir: Fail2BanConfigDirDep,
_auth: AuthDep,
name: _NamePath,
) -> ConfFileContent:
@@ -336,7 +331,6 @@ async def get_filter_file_raw(
HTTPException: 404 if the file does not exist.
HTTPException: 503 if the config directory is unavailable.
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
return await raw_config_io_service.get_filter_file(config_dir, name)
except ConfigFileNameError as exc:
@@ -353,7 +347,7 @@ async def get_filter_file_raw(
summary="Update a filter definition file (raw content)",
)
async def write_filter_file(
request: Request,
config_dir: Fail2BanConfigDirDep,
_auth: AuthDep,
name: _NamePath,
body: ConfFileUpdateRequest,
@@ -371,7 +365,6 @@ async def write_filter_file(
HTTPException: 404 if the file does not exist.
HTTPException: 503 if the config directory is unavailable.
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
await raw_config_io_service.write_filter_file(config_dir, name, body)
except ConfigFileNameError as exc:
@@ -391,7 +384,7 @@ async def write_filter_file(
summary="Create a new filter definition file (raw content)",
)
async def create_filter_file(
request: Request,
config_dir: Fail2BanConfigDirDep,
_auth: AuthDep,
body: ConfFileCreateRequest,
) -> ConfFileContent:
@@ -410,7 +403,6 @@ async def create_filter_file(
HTTPException: 409 if a file with that name already exists.
HTTPException: 503 if the config directory is unavailable.
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
filename = await raw_config_io_service.create_filter_file(config_dir, body)
except ConfigFileNameError as exc:
@@ -440,7 +432,7 @@ async def create_filter_file(
summary="List all action definition files",
)
async def list_action_files(
request: Request,
config_dir: Fail2BanConfigDirDep,
_auth: AuthDep,
) -> ConfFilesResponse:
"""Return a list of every ``.conf`` and ``.local`` file in ``action.d/``.
@@ -452,7 +444,6 @@ async def list_action_files(
Returns:
:class:`~app.models.file_config.ConfFilesResponse`.
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
return await raw_config_io_service.list_action_files(config_dir)
except ConfigDirError as exc:
@@ -465,7 +456,7 @@ async def list_action_files(
summary="Return an action definition file with its content",
)
async def get_action_file(
request: Request,
config_dir: Fail2BanConfigDirDep,
_auth: AuthDep,
name: _NamePath,
) -> ConfFileContent:
@@ -484,7 +475,6 @@ async def get_action_file(
HTTPException: 404 if the file does not exist.
HTTPException: 503 if the config directory is unavailable.
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
return await raw_config_io_service.get_action_file(config_dir, name)
except ConfigFileNameError as exc:
@@ -501,7 +491,7 @@ async def get_action_file(
summary="Update an action definition file",
)
async def write_action_file(
request: Request,
config_dir: Fail2BanConfigDirDep,
_auth: AuthDep,
name: _NamePath,
body: ConfFileUpdateRequest,
@@ -519,7 +509,6 @@ async def write_action_file(
HTTPException: 404 if the file does not exist.
HTTPException: 503 if the config directory is unavailable.
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
await raw_config_io_service.write_action_file(config_dir, name, body)
except ConfigFileNameError as exc:
@@ -539,7 +528,7 @@ async def write_action_file(
summary="Create a new action definition file",
)
async def create_action_file(
request: Request,
config_dir: Fail2BanConfigDirDep,
_auth: AuthDep,
body: ConfFileCreateRequest,
) -> ConfFileContent:
@@ -558,7 +547,6 @@ async def create_action_file(
HTTPException: 409 if a file with that name already exists.
HTTPException: 503 if the config directory is unavailable.
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
filename = await raw_config_io_service.create_action_file(config_dir, body)
except ConfigFileNameError as exc:
@@ -588,7 +576,7 @@ async def create_action_file(
summary="Return a filter file parsed into a structured model",
)
async def get_parsed_filter(
request: Request,
config_dir: Fail2BanConfigDirDep,
_auth: AuthDep,
name: _NamePath,
) -> FilterConfig:
@@ -611,7 +599,6 @@ async def get_parsed_filter(
HTTPException: 404 if the file does not exist.
HTTPException: 503 if the config directory is unavailable.
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
return await raw_config_io_service.get_parsed_filter_file(config_dir, name)
except ConfigFileNameError as exc:
@@ -628,7 +615,7 @@ async def get_parsed_filter(
summary="Update a filter file from a structured model",
)
async def update_parsed_filter(
request: Request,
config_dir: Fail2BanConfigDirDep,
_auth: AuthDep,
name: _NamePath,
body: FilterConfigUpdate,
@@ -649,7 +636,6 @@ async def update_parsed_filter(
HTTPException: 404 if the file does not exist.
HTTPException: 503 if the config directory is unavailable.
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
await raw_config_io_service.update_parsed_filter_file(config_dir, name, body)
except ConfigFileNameError as exc:
@@ -673,7 +659,7 @@ async def update_parsed_filter(
summary="Return an action file parsed into a structured model",
)
async def get_parsed_action(
request: Request,
config_dir: Fail2BanConfigDirDep,
_auth: AuthDep,
name: _NamePath,
) -> ActionConfig:
@@ -696,7 +682,6 @@ async def get_parsed_action(
HTTPException: 404 if the file does not exist.
HTTPException: 503 if the config directory is unavailable.
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
return await raw_config_io_service.get_parsed_action_file(config_dir, name)
except ConfigFileNameError as exc:
@@ -713,7 +698,7 @@ async def get_parsed_action(
summary="Update an action file from a structured model",
)
async def update_parsed_action(
request: Request,
config_dir: Fail2BanConfigDirDep,
_auth: AuthDep,
name: _NamePath,
body: ActionConfigUpdate,
@@ -734,7 +719,6 @@ async def update_parsed_action(
HTTPException: 404 if the file does not exist.
HTTPException: 503 if the config directory is unavailable.
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
await raw_config_io_service.update_parsed_action_file(config_dir, name, body)
except ConfigFileNameError as exc:
@@ -758,7 +742,7 @@ async def update_parsed_action(
summary="Return a jail.d file parsed into a structured model",
)
async def get_parsed_jail_file(
request: Request,
config_dir: Fail2BanConfigDirDep,
_auth: AuthDep,
filename: _NamePath,
) -> JailFileConfig:
@@ -781,7 +765,6 @@ async def get_parsed_jail_file(
HTTPException: 404 if the file does not exist.
HTTPException: 503 if the config directory is unavailable.
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
return await raw_config_io_service.get_parsed_jail_file(config_dir, filename)
except ConfigFileNameError as exc:
@@ -798,7 +781,7 @@ async def get_parsed_jail_file(
summary="Update a jail.d file from a structured model",
)
async def update_parsed_jail_file(
request: Request,
config_dir: Fail2BanConfigDirDep,
_auth: AuthDep,
filename: _NamePath,
body: JailFileConfigUpdate,
@@ -819,7 +802,6 @@ async def update_parsed_jail_file(
HTTPException: 404 if the file does not exist.
HTTPException: 503 if the config directory is unavailable.
"""
config_dir: str = request.app.state.settings.fail2ban_config_dir
try:
await raw_config_io_service.update_parsed_jail_file(config_dir, filename, body)
except ConfigFileNameError as exc:

View File

@@ -16,7 +16,7 @@ if TYPE_CHECKING:
from app.services.jail_service import IpLookupResult
import aiosqlite
from fastapi import APIRouter, Depends, HTTPException, Path, Request, status
from fastapi import APIRouter, Depends, HTTPException, Path, status
from app.dependencies import (
AuthDep,
@@ -40,7 +40,6 @@ _IpPath = Annotated[str, Path(description="IPv4 or IPv6 address to look up.")]
summary="Look up ban status and geo information for an IP",
)
async def lookup_ip(
request: Request,
_auth: AuthDep,
ip: _IpPath,
socket_path: Fail2BanSocketDep,
@@ -53,7 +52,6 @@ async def lookup_ip(
organisation data from ip-api.com.
Args:
request: Incoming request (used to access ``app.state``).
_auth: Validated session — enforces authentication.
ip: The IP address to look up.
@@ -140,7 +138,6 @@ async def geo_stats(
summary="Re-resolve all IPs whose country could not be determined",
)
async def re_resolve_geo(
request: Request,
_auth: AuthDep,
db: Annotated[aiosqlite.Connection, Depends(get_db)],
http_session: HttpSessionDep,
@@ -151,9 +148,9 @@ async def re_resolve_geo(
are immediately eligible for a new API attempt.
Args:
request: Incoming request (used to access ``app.state.http_session``).
_auth: Validated session — enforces authentication.
db: BanGUI application database (for reading/writing ``geo_cache``).
http_session: Shared HTTP session for geo lookups.
Returns:
JSON object ``{"resolved": N, "total": M}`` where *N* is the number