f05aaa707b
Alembic 0032 adds Source.error_type (varchar(32), indexed).
_update_source_health stamps it alongside last_error on status='error'
and clears it on 'ok'. SourceRecord/to_dict exposes it.
FailingSourcesCard renders a colored chip next to the consecutive-
failures count, with a tooltip explaining the suggested operator
action. Color reflects intent:
- warning (yellow) — operator action needed (auth_error)
- info (blue) — backend-paced (rate_limited / timeout /
network_error / partial / tier_limited)
- error (red) — likely terminal without intervention
(not_found / access_denied / validation_failed /
unsupported_url / http_error / unknown_error)
Audit 2026-06-02: the backend computed 13 ErrorType categories but
only the free-text last_error reached the operator. Bulk-triage by
class ("all auth_error → rotate cookies", "12 rate_limited → just
wait") required opening Logs per row.
47 lines
2.1 KiB
Python
47 lines
2.1 KiB
Python
"""Source — a platform-specific URL owned by an Artist (e.g., a Patreon URL).
|
|
|
|
Multiple sources per artist support creators with cross-platform presence.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import JSON, Boolean, DateTime, ForeignKey, Integer, String, Text
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from .base import Base
|
|
|
|
|
|
class Source(Base):
|
|
__tablename__ = "source"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
artist_id: Mapped[int] = mapped_column(
|
|
ForeignKey("artist.id", ondelete="CASCADE"), nullable=False, index=True
|
|
)
|
|
platform: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
url: Mapped[str] = mapped_column(Text, nullable=False)
|
|
enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
|
|
|
config_overrides: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
|
|
last_checked_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
last_error: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
# alembic 0032: last ErrorType category (auth_error, rate_limited,
|
|
# not_found, ...). Lets FailingSourcesCard surface the taxonomy as
|
|
# a colored chip so operators can bulk-triage by error class. Set
|
|
# by _update_source_health alongside last_error; cleared on 'ok'.
|
|
error_type: Mapped[str | None] = mapped_column(String(32), nullable=True, index=True)
|
|
check_interval_override: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
consecutive_failures: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
|
|
|
# alembic 0031: sticky deep-scan budget. When > 0, the next N download
|
|
# runs use gallery-dl's full-walk config (skip: True + 1800s timeout);
|
|
# when 0, runs use tick mode (skip: "exit:20" + 870s, exits early once
|
|
# 20 contiguous archived items are seen). Auto-decrements per run, with
|
|
# an auto-reset to 0 on clean exit + zero downloads (queue drained).
|
|
backfill_runs_remaining: Mapped[int] = mapped_column(
|
|
Integer, nullable=False, default=0, server_default="0",
|
|
)
|
|
|
|
artist = relationship("Artist", back_populates="sources")
|