Enforce repository boundary: Remove DbDep from routers
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>
This commit is contained in:
@@ -374,6 +374,169 @@ async def get_fail2ban_metadata_service() -> object:
|
||||
return default_fail2ban_metadata_service
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Service facade dependencies (db + repositories combined)
|
||||
# These are for routers that need database access through services.
|
||||
# Routers should depend on these instead of raw database connections.
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
|
||||
@dataclass
|
||||
class SessionServiceContext:
|
||||
"""Context for session-related database operations.
|
||||
|
||||
Combines the database connection and session repository so that
|
||||
routers don't need to import DbDep directly.
|
||||
"""
|
||||
|
||||
db: aiosqlite.Connection
|
||||
session_repo: SessionRepository
|
||||
|
||||
|
||||
async def get_session_service_context(
|
||||
db: Annotated[aiosqlite.Connection, Depends(get_db)],
|
||||
session_repo: Annotated[SessionRepository, Depends(get_session_repo)],
|
||||
) -> SessionServiceContext:
|
||||
"""Provide combined session database context for routers.
|
||||
|
||||
Args:
|
||||
db: Request-scoped database connection.
|
||||
session_repo: Session repository implementation.
|
||||
|
||||
Returns:
|
||||
SessionServiceContext with both db and repository.
|
||||
"""
|
||||
return SessionServiceContext(db=db, session_repo=session_repo)
|
||||
|
||||
|
||||
@dataclass
|
||||
class BlocklistServiceContext:
|
||||
"""Context for blocklist-related database operations.
|
||||
|
||||
Combines the database connection and blocklist-related repositories
|
||||
so that routers don't need to import DbDep directly.
|
||||
"""
|
||||
|
||||
db: aiosqlite.Connection
|
||||
blocklist_repo: BlocklistRepository
|
||||
import_log_repo: ImportLogRepository
|
||||
settings_repo: SettingsRepository
|
||||
|
||||
|
||||
async def get_blocklist_service_context(
|
||||
db: Annotated[aiosqlite.Connection, Depends(get_db)],
|
||||
blocklist_repo: Annotated[BlocklistRepository, Depends(get_blocklist_repo)],
|
||||
import_log_repo: Annotated[ImportLogRepository, Depends(get_import_log_repo)],
|
||||
settings_repo: Annotated[SettingsRepository, Depends(get_settings_repo)],
|
||||
) -> BlocklistServiceContext:
|
||||
"""Provide combined blocklist database context for routers.
|
||||
|
||||
Args:
|
||||
db: Request-scoped database connection.
|
||||
blocklist_repo: Blocklist repository implementation.
|
||||
import_log_repo: Import log repository implementation.
|
||||
settings_repo: Settings repository implementation.
|
||||
|
||||
Returns:
|
||||
BlocklistServiceContext with db and all blocklist repositories.
|
||||
"""
|
||||
return BlocklistServiceContext(
|
||||
db=db,
|
||||
blocklist_repo=blocklist_repo,
|
||||
import_log_repo=import_log_repo,
|
||||
settings_repo=settings_repo,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SettingsServiceContext:
|
||||
"""Context for settings-related database operations.
|
||||
|
||||
Combines the database connection and settings repository so that
|
||||
routers don't need to import DbDep directly.
|
||||
"""
|
||||
|
||||
db: aiosqlite.Connection
|
||||
settings_repo: SettingsRepository
|
||||
|
||||
|
||||
async def get_settings_service_context(
|
||||
db: Annotated[aiosqlite.Connection, Depends(get_db)],
|
||||
settings_repo: Annotated[SettingsRepository, Depends(get_settings_repo)],
|
||||
) -> SettingsServiceContext:
|
||||
"""Provide combined settings database context for routers.
|
||||
|
||||
Args:
|
||||
db: Request-scoped database connection.
|
||||
settings_repo: Settings repository implementation.
|
||||
|
||||
Returns:
|
||||
SettingsServiceContext with both db and repository.
|
||||
"""
|
||||
return SettingsServiceContext(db=db, settings_repo=settings_repo)
|
||||
|
||||
|
||||
@dataclass
|
||||
class BanServiceContext:
|
||||
"""Context for ban-related database operations.
|
||||
|
||||
Combines the database connection and fail2ban DB repository.
|
||||
"""
|
||||
|
||||
db: aiosqlite.Connection
|
||||
fail2ban_db_repo: Fail2BanDbRepository
|
||||
|
||||
|
||||
async def get_ban_service_context(
|
||||
db: Annotated[aiosqlite.Connection, Depends(get_db)],
|
||||
fail2ban_db_repo: Annotated[Fail2BanDbRepository, Depends(get_fail2ban_db_repo)],
|
||||
) -> BanServiceContext:
|
||||
"""Provide combined ban database context for routers.
|
||||
|
||||
Args:
|
||||
db: Request-scoped database connection.
|
||||
fail2ban_db_repo: Fail2Ban DB repository implementation.
|
||||
|
||||
Returns:
|
||||
BanServiceContext with both db and repository.
|
||||
"""
|
||||
return BanServiceContext(db=db, fail2ban_db_repo=fail2ban_db_repo)
|
||||
|
||||
|
||||
@dataclass
|
||||
class HistoryServiceContext:
|
||||
"""Context for history-related database operations.
|
||||
|
||||
Combines database connection and history-related repositories.
|
||||
"""
|
||||
|
||||
db: aiosqlite.Connection
|
||||
fail2ban_db_repo: Fail2BanDbRepository
|
||||
history_archive_repo: HistoryArchiveRepository
|
||||
|
||||
|
||||
async def get_history_service_context(
|
||||
db: Annotated[aiosqlite.Connection, Depends(get_db)],
|
||||
fail2ban_db_repo: Annotated[Fail2BanDbRepository, Depends(get_fail2ban_db_repo)],
|
||||
history_archive_repo: Annotated[HistoryArchiveRepository, Depends(get_history_archive_repo)],
|
||||
) -> HistoryServiceContext:
|
||||
"""Provide combined history database context for routers.
|
||||
|
||||
Args:
|
||||
db: Request-scoped database connection.
|
||||
fail2ban_db_repo: Fail2Ban DB repository implementation.
|
||||
history_archive_repo: History archive repository implementation.
|
||||
|
||||
Returns:
|
||||
HistoryServiceContext with db and all history repositories.
|
||||
"""
|
||||
return HistoryServiceContext(
|
||||
db=db,
|
||||
fail2ban_db_repo=fail2ban_db_repo,
|
||||
history_archive_repo=history_archive_repo,
|
||||
)
|
||||
|
||||
|
||||
# Internal database dependency for use by other dependencies only
|
||||
# Routers should NOT import this - they should use repository dependencies instead
|
||||
_DbDep = Annotated[aiosqlite.Connection, Depends(get_db)]
|
||||
@@ -482,6 +645,14 @@ AuthDep = Annotated[Session, Depends(require_auth)]
|
||||
LoginRateLimiterDep = Annotated[RateLimiter, Depends(get_login_rate_limiter)]
|
||||
Fail2BanMetadataServiceDep = Annotated[Fail2BanMetadataService, Depends(get_fail2ban_metadata_service)]
|
||||
|
||||
# Service context dependencies (db + repositories combined for routers)
|
||||
# Routers should use these instead of importing DbDep directly.
|
||||
SessionServiceContextDep = Annotated[SessionServiceContext, Depends(get_session_service_context)]
|
||||
BlocklistServiceContextDep = Annotated[BlocklistServiceContext, Depends(get_blocklist_service_context)]
|
||||
SettingsServiceContextDep = Annotated[SettingsServiceContext, Depends(get_settings_service_context)]
|
||||
BanServiceContextDep = Annotated[BanServiceContext, Depends(get_ban_service_context)]
|
||||
HistoryServiceContextDep = Annotated[HistoryServiceContext, Depends(get_history_service_context)]
|
||||
|
||||
# DEPRECATED: DbDep is provided for backward compatibility only.
|
||||
# DO NOT use in new code. Use repository dependencies instead (SessionRepoDep, BlocklistRepositoryDep, etc.)
|
||||
# See Backend-Development.md § 6 for dependency layering rules.
|
||||
|
||||
Reference in New Issue
Block a user