refactor: Make service dependencies explicit and injectable
Remove hidden cross-service coupling by making dependencies explicit through dependency injection while maintaining backward compatibility via lazy imports. Key changes: - history_service and ban_service: Removed direct module-level imports of fail2ban_metadata_service, added optional service parameters to functions - Added get_fail2ban_metadata_service() provider to dependencies.py - Updated history router to inject Fail2BanMetadataService dependency - history_service functions now use lazy imports in fallback paths for backward compatibility when service is not explicitly injected - All test patches updated to use internal _get_fail2ban_db_path() helper - jail_config_service and jail_service already follow best practices This pattern prevents circular imports, makes services testable via explicit mocking, and documents service dependencies clearly. Fixes: Instructions.md #2 - Hidden cross-service coupling Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -24,6 +24,7 @@ from app.dependencies import (
|
||||
DbDep,
|
||||
Fail2BanSocketDep,
|
||||
HttpSessionDep,
|
||||
Fail2BanMetadataServiceDep,
|
||||
)
|
||||
from app.models.ban import BanOrigin, TimeRange
|
||||
from app.models.history import HistoryListResponse, IpDetailResponse
|
||||
@@ -44,6 +45,7 @@ async def get_history(
|
||||
db: DbDep,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
http_session: HttpSessionDep,
|
||||
fail2ban_metadata_service: Fail2BanMetadataServiceDep,
|
||||
range: TimeRange | None = Query(
|
||||
default=None,
|
||||
description="Optional time-range filter. Omit for all-time.",
|
||||
@@ -102,6 +104,7 @@ async def get_history(
|
||||
page_size=page_size,
|
||||
http_session=http_session,
|
||||
db=db,
|
||||
fail2ban_metadata_service=fail2ban_metadata_service,
|
||||
)
|
||||
|
||||
|
||||
@@ -116,6 +119,7 @@ async def get_history_archive(
|
||||
db: DbDep,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
http_session: HttpSessionDep,
|
||||
fail2ban_metadata_service: Fail2BanMetadataServiceDep,
|
||||
range: TimeRange | None = Query(
|
||||
default=None,
|
||||
description="Optional time-range filter. Omit for all-time.",
|
||||
@@ -136,6 +140,7 @@ async def get_history_archive(
|
||||
page_size=page_size,
|
||||
http_session=http_session,
|
||||
db=db,
|
||||
fail2ban_metadata_service=fail2ban_metadata_service,
|
||||
)
|
||||
|
||||
|
||||
@@ -150,6 +155,7 @@ async def get_ip_history(
|
||||
ip: str,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
http_session: HttpSessionDep,
|
||||
fail2ban_metadata_service: Fail2BanMetadataServiceDep,
|
||||
) -> IpDetailResponse:
|
||||
"""Return the complete historical record for a single IP address.
|
||||
|
||||
@@ -174,6 +180,7 @@ async def get_ip_history(
|
||||
socket_path,
|
||||
ip,
|
||||
http_session=http_session,
|
||||
fail2ban_metadata_service=fail2ban_metadata_service,
|
||||
)
|
||||
|
||||
if detail is None:
|
||||
|
||||
@@ -11,6 +11,7 @@ from app.dependencies import (
|
||||
Fail2BanConfigDirDep,
|
||||
Fail2BanSocketDep,
|
||||
Fail2BanStartCommandDep,
|
||||
HealthProbeDep,
|
||||
PendingRecoveryDep,
|
||||
)
|
||||
from app.models.config import (
|
||||
@@ -277,6 +278,7 @@ async def activate_jail(
|
||||
_auth: AuthDep,
|
||||
config_dir: Fail2BanConfigDirDep,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
health_probe: HealthProbeDep,
|
||||
name: _NamePath,
|
||||
body: ActivateJailRequest | None = None,
|
||||
) -> JailActivationResponse:
|
||||
@@ -289,6 +291,9 @@ async def activate_jail(
|
||||
Args:
|
||||
app: FastAPI application instance.
|
||||
_auth: Validated session.
|
||||
config_dir: Absolute path to the fail2ban configuration directory.
|
||||
socket_path: Path to the fail2ban Unix domain socket.
|
||||
health_probe: Injectable health probe function for checking fail2ban status.
|
||||
name: Name of the jail to activate.
|
||||
body: Optional override values (bantime, findtime, maxretry, port,
|
||||
logpath).
|
||||
@@ -304,7 +309,9 @@ async def activate_jail(
|
||||
"""
|
||||
req = body if body is not None else ActivateJailRequest()
|
||||
|
||||
result = await jail_config_service.activate_jail(config_dir, socket_path, name, req)
|
||||
result = await jail_config_service.activate_jail(
|
||||
config_dir, socket_path, name, req, health_probe=health_probe
|
||||
)
|
||||
|
||||
if result.active:
|
||||
record_activation(app, name)
|
||||
@@ -323,6 +330,7 @@ async def deactivate_jail(
|
||||
_auth: AuthDep,
|
||||
config_dir: Fail2BanConfigDirDep,
|
||||
socket_path: Fail2BanSocketDep,
|
||||
health_probe: HealthProbeDep,
|
||||
name: _NamePath,
|
||||
) -> JailActivationResponse:
|
||||
"""Disable an active jail and reload fail2ban.
|
||||
@@ -332,6 +340,9 @@ async def deactivate_jail(
|
||||
|
||||
Args:
|
||||
_auth: Validated session.
|
||||
config_dir: Absolute path to the fail2ban configuration directory.
|
||||
socket_path: Path to the fail2ban Unix domain socket.
|
||||
health_probe: Injectable health probe function for checking fail2ban status.
|
||||
name: Name of the jail to deactivate.
|
||||
|
||||
Returns:
|
||||
@@ -344,7 +355,9 @@ async def deactivate_jail(
|
||||
HTTPException: 502 if fail2ban is unreachable.
|
||||
"""
|
||||
|
||||
result = await jail_config_service.deactivate_jail(config_dir, socket_path, name)
|
||||
result = await jail_config_service.deactivate_jail(
|
||||
config_dir, socket_path, name, health_probe=health_probe
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user