From 8ef748c7b8fb0320a6304f28a2267ee4f21eefe3 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Fri, 26 Jun 2026 03:15:36 +0200 Subject: [PATCH] fix(maintenance): drop dead status=="saved" scan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Watch later became a built-in playlist, so "saved" is no longer a VideoState status — the scan matched nothing and under-counted users impacted by a video removal. The playlist loop already covers Watch later, so impact is now correct. Route the local _now through the shared utils.now_utc. --- backend/app/sync/maintenance.py | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/backend/app/sync/maintenance.py b/backend/app/sync/maintenance.py index 80c0129..1d35003 100644 --- a/backend/app/sync/maintenance.py +++ b/backend/app/sync/maintenance.py @@ -20,18 +20,19 @@ Quota: phase 1 costs ~(flagged / 50) units, phase 2 ~(batch / 50). Both respect backfill reserve as the other jobs so interactive syncs never starve. """ import logging -from datetime import datetime, timedelta, timezone +from datetime import timedelta -from sqlalchemy import or_, select +from sqlalchemy import select from sqlalchemy.orm import Session from app import progress, quota, sysconfig from app.config import settings -from app.models import Playlist, PlaylistItem, Video, VideoState +from app.models import Playlist, PlaylistItem, Video from app.notifications import create_notification from app.state import get_maintenance_batch from app.sync.runner import get_service_user from app.sync.videos import apply_video_details, parse_dt +from app.utils import now_utc as _now from app.youtube.client import YouTubeClient log = logging.getLogger("siftlode.maintenance") @@ -45,10 +46,6 @@ GRACE_DAYS_BY_REASON = { DEFAULT_GRACE_DAYS = settings.maintenance_grace_days -def _now() -> datetime: - return datetime.now(timezone.utc) - - def _grace_days(reason: str | None) -> int: return GRACE_DAYS_BY_REASON.get(reason or "", DEFAULT_GRACE_DAYS) @@ -91,14 +88,9 @@ def _collect_user_impact(db: Session, videos: list[Video]) -> dict[int, list[dic impact: dict[int, set[str]] = {} if not ids: return {} - # Saved (VideoState.status == "saved"). - for user_id, video_id in db.execute( - select(VideoState.user_id, VideoState.video_id).where( - VideoState.video_id.in_(ids), VideoState.status == "saved" - ) - ): - impact.setdefault(user_id, set()).add(video_id) - # In any of the user's playlists (covers watch-later, which is a playlist). + # Anyone who had it in a playlist — which now also covers "saved": Watch later became a + # built-in playlist, so "saved" is no longer a VideoState status (the old status=="saved" + # scan matched nothing and under-counted impacted users). for user_id, video_id in db.execute( select(Playlist.user_id, PlaylistItem.video_id) .join(PlaylistItem, PlaylistItem.playlist_id == Playlist.id)