Fix Issue 7: Enforce repository pattern consistency

- Added 5 new service methods for complete database coverage:
  * get_series_without_nfo()
  * count_all()
  * count_with_nfo()
  * count_with_tmdb_id()
  * count_with_tvdb_id()

- Eliminated all direct database queries from business logic:
  * series_manager_service.py - now uses AnimeSeriesService
  * anime_service.py - now uses service layer methods

- Documented architecture decision in ARCHITECTURE.md:
  * Service layer IS the repository layer
  * No direct SQLAlchemy queries allowed outside service layer

- All database access must go through service methods
- 1449 tests passing, repository pattern enforced
This commit is contained in:
2026-01-24 21:20:17 +01:00
parent 35a7aeac9e
commit ed3882991f
5 changed files with 237 additions and 130 deletions

View File

@@ -297,6 +297,111 @@ class AnimeSeriesService:
result = await db.execute(query)
return list(result.scalars().all())
@staticmethod
async def get_series_without_nfo(
db: AsyncSession,
limit: Optional[int] = None,
offset: int = 0,
) -> List[AnimeSeries]:
"""Get anime series without NFO files.
Returns series where has_nfo is False.
Args:
db: Database session
limit: Optional limit for results
offset: Offset for pagination
Returns:
List of AnimeSeries without NFO files
"""
query = (
select(AnimeSeries)
.where(AnimeSeries.has_nfo == False) # noqa: E712
.order_by(AnimeSeries.name)
.offset(offset)
)
if limit:
query = query.limit(limit)
result = await db.execute(query)
return list(result.scalars().all())
@staticmethod
async def count_all(db: AsyncSession) -> int:
"""Count total number of anime series.
Args:
db: Database session
Returns:
Total count of series
"""
from sqlalchemy import func
result = await db.execute(
select(func.count()).select_from(AnimeSeries)
)
return result.scalar() or 0
@staticmethod
async def count_with_nfo(db: AsyncSession) -> int:
"""Count anime series with NFO files.
Args:
db: Database session
Returns:
Count of series with has_nfo=True
"""
from sqlalchemy import func
result = await db.execute(
select(func.count())
.select_from(AnimeSeries)
.where(AnimeSeries.has_nfo == True) # noqa: E712
)
return result.scalar() or 0
@staticmethod
async def count_with_tmdb_id(db: AsyncSession) -> int:
"""Count anime series with TMDB ID.
Args:
db: Database session
Returns:
Count of series with tmdb_id set
"""
from sqlalchemy import func
result = await db.execute(
select(func.count())
.select_from(AnimeSeries)
.where(AnimeSeries.tmdb_id.isnot(None))
)
return result.scalar() or 0
@staticmethod
async def count_with_tvdb_id(db: AsyncSession) -> int:
"""Count anime series with TVDB ID.
Args:
db: Database session
Returns:
Count of series with tvdb_id set
"""
from sqlalchemy import func
result = await db.execute(
select(func.count())
.select_from(AnimeSeries)
.where(AnimeSeries.tvdb_id.isnot(None))
)
return result.scalar() or 0
# ============================================================================