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:
2026-04-27 18:26:08 +02:00
parent bc315b936b
commit 3bbf413c55
12 changed files with 342 additions and 100 deletions

View File

@@ -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: