Refactor router dependency wiring to explicit app state providers
This commit is contained in:
@@ -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)]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user