fc3d: SourceRecord surfaces consecutive_failures + server-derived next_check_at
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,8 +8,9 @@ from sqlalchemy import func, select
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from ..models import Artist, Source
|
||||
from ..models import Artist, ImportSettings, Source
|
||||
from .platforms import known_platform_keys
|
||||
from .scheduler_service import compute_next_check_at
|
||||
|
||||
# Re-exported for back-compat with FC-3a; the registry in
|
||||
# services/platforms.py is the single source of truth.
|
||||
@@ -59,6 +60,8 @@ class SourceRecord:
|
||||
last_checked_at: str | None
|
||||
last_error: str | None
|
||||
check_interval_override: int | None
|
||||
consecutive_failures: int
|
||||
next_check_at: str | None
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
@@ -73,6 +76,8 @@ class SourceRecord:
|
||||
"last_checked_at": self.last_checked_at,
|
||||
"last_error": self.last_error,
|
||||
"check_interval_override": self.check_interval_override,
|
||||
"consecutive_failures": self.consecutive_failures,
|
||||
"next_check_at": self.next_check_at,
|
||||
}
|
||||
|
||||
|
||||
@@ -114,10 +119,15 @@ class SourceService:
|
||||
raise InvalidConfigError("config_overrides must be a JSON object")
|
||||
return config
|
||||
|
||||
async def _row_to_record(self, source: Source) -> SourceRecord:
|
||||
artist = (await self.session.execute(
|
||||
select(Artist.name, Artist.slug).where(Artist.id == source.artist_id)
|
||||
)).one()
|
||||
async def _load_settings(self) -> ImportSettings:
|
||||
return (await self.session.execute(
|
||||
select(ImportSettings).where(ImportSettings.id == 1)
|
||||
)).scalar_one()
|
||||
|
||||
def _build_record(
|
||||
self, source: Source, artist: Artist, settings: ImportSettings,
|
||||
) -> SourceRecord:
|
||||
nxt = compute_next_check_at(source, artist, settings)
|
||||
return SourceRecord(
|
||||
id=source.id,
|
||||
artist_id=source.artist_id,
|
||||
@@ -130,29 +140,28 @@ class SourceService:
|
||||
last_checked_at=source.last_checked_at.isoformat() if source.last_checked_at else None,
|
||||
last_error=source.last_error,
|
||||
check_interval_override=source.check_interval_override,
|
||||
consecutive_failures=source.consecutive_failures or 0,
|
||||
next_check_at=nxt.isoformat() if nxt else None,
|
||||
)
|
||||
|
||||
async def _row_to_record(self, source: Source) -> SourceRecord:
|
||||
artist = (await self.session.execute(
|
||||
select(Artist).where(Artist.id == source.artist_id)
|
||||
)).scalar_one()
|
||||
settings = await self._load_settings()
|
||||
return self._build_record(source, artist, settings)
|
||||
|
||||
async def list(self, artist_id: int | None = None) -> list[SourceRecord]:
|
||||
stmt = (
|
||||
select(Source, Artist.name, Artist.slug)
|
||||
select(Source, Artist)
|
||||
.join(Artist, Artist.id == Source.artist_id)
|
||||
.order_by(Artist.name.asc(), Source.id.asc())
|
||||
)
|
||||
if artist_id is not None:
|
||||
stmt = stmt.where(Source.artist_id == artist_id)
|
||||
rows = (await self.session.execute(stmt)).all()
|
||||
return [
|
||||
SourceRecord(
|
||||
id=s.id, artist_id=s.artist_id,
|
||||
artist_name=name, artist_slug=slug,
|
||||
platform=s.platform, url=s.url, enabled=s.enabled,
|
||||
config_overrides=s.config_overrides,
|
||||
last_checked_at=s.last_checked_at.isoformat() if s.last_checked_at else None,
|
||||
last_error=s.last_error,
|
||||
check_interval_override=s.check_interval_override,
|
||||
)
|
||||
for s, name, slug in rows
|
||||
]
|
||||
settings = await self._load_settings()
|
||||
return [self._build_record(s, a, settings) for s, a in rows]
|
||||
|
||||
async def get(self, source_id: int) -> SourceRecord | None:
|
||||
source = (await self.session.execute(
|
||||
|
||||
Reference in New Issue
Block a user