When gallery-dl logs "[patreon][error] Failed to extract campaign ID"
but then recovers via its /cw/<vanity> fallback and skips every file
(all already archived), _categorize_error was flagging the [error] line
as fatal and returning UNKNOWN_ERROR. Add "failed to extract campaign
id" to the per-item error allowlist alongside "not allowed to view" and
"unable to get post" so skip detection proceeds and the run is
classified NO_NEW_CONTENT.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Converts the Source Health last-check column and the Downloads created-at
column from locale date strings to human-relative times ("6 hours ago").
The absolute timestamp is preserved as a native browser tooltip so full
precision is still one hover away; detail modals keep the absolute form.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Added missing 'from sqlalchemy.ext.asyncio import AsyncSession' import in downloads.py
This fixes the NameError at module load time that was breaking all download tasks
- Replaced regex patterns with plain-string equivalents in NOT_FOUND_PATTERNS:
* 'account.*deleted' → 'account deleted' (. and * are literal in substring search)
* 'profile.*not.*exist' → 'profile does not exist' (same issue)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace all bare `session = AsyncSession(bind=conn)` assignments with
`async with AsyncSession(bind=conn) as session:` in downloads.py,
credentials.py, settings.py, sources.py, and subscriptions.py to
ensure sessions are properly closed on all code paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract identical get_async_session() and cleanup_engine() functions
from downloads.py and maintenance.py into a shared db.py module.
This eliminates code duplication and improves maintainability.
db.py imports only from app.config and SQLAlchemy to avoid circular
dependencies with the task modules that import from it.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>