Layering cleanup, no behaviour change (ruff clean backend+scripts, pytest 193): - Invert the scheduler->routes dependency: reset_demo_state (+ its demo-seed helpers) move from routes/admin.py to a new service module app/demo.py. Both routes/admin.py and scheduler.py now import it from app.demo, so the scheduler no longer reaches into the routes layer — its lazy import (the routes<->scheduler cycle workaround) is gone, hoisted to a top-level import. - Publish the private symbols that were imported across module boundaries (the underscore convention was lying): feed._serialize -> serialize, feed._to_tsquery_str -> to_tsquery_str, videos._insert_stubs -> insert_stubs, videos._stub_from_playlist_item -> stub_from_playlist_item, worker._uploader_url -> uploader_url, plex._shared._item_or_404 -> item_or_404. Scoped renames — the 5 other route modules keep their own truly private _serialize helpers. - _release_asset moves from routes/downloads.py to app/downloads/service.py as the public release_asset (asset bookkeeping is a service concern); the routes call it via service.release_asset.
78 lines
3.1 KiB
Python
78 lines
3.1 KiB
Python
"""Demo-account service: reset the shared demo sandbox to a clean, seeded baseline.
|
|
|
|
Shared by the admin's manual reset endpoint (routes/admin.py) and the scheduled demo-reset job
|
|
(scheduler.py). Lives in the service layer, not routes, so the scheduler can call it without an
|
|
`app.routes -> app.scheduler` import cycle (the reason its import used to be lazy).
|
|
"""
|
|
from sqlalchemy import delete, func, select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.models import Playlist, PlaylistItem, Subscription, User, Video, VideoState
|
|
|
|
|
|
def _seed_demo_playlists(db: Session, demo: User) -> int:
|
|
"""Seed a few sample playlists from the shared catalog so a freshly-reset demo isn't
|
|
empty — gives visitors something to play with. Best-effort: skips silently if the
|
|
catalog is too small."""
|
|
recent = (
|
|
db.execute(
|
|
select(Video.id)
|
|
.where(Video.is_short.is_(False), Video.live_status == "none")
|
|
.order_by(Video.published_at.desc().nullslast())
|
|
.limit(60)
|
|
)
|
|
.scalars()
|
|
.all()
|
|
)
|
|
if not recent:
|
|
return 0
|
|
samples = [
|
|
("Fresh picks", recent[0:10]),
|
|
("Quick watch", recent[10:18]),
|
|
("Saved for later", recent[18:26]),
|
|
]
|
|
created = 0
|
|
for pos, (name, vids) in enumerate(samples):
|
|
if not vids:
|
|
continue
|
|
pl = Playlist(user_id=demo.id, name=name, position=pos)
|
|
db.add(pl)
|
|
db.flush()
|
|
for ipos, vid in enumerate(vids):
|
|
db.add(PlaylistItem(playlist_id=pl.id, video_id=vid, position=ipos))
|
|
created += 1
|
|
return created
|
|
|
|
|
|
def _seed_demo_subscriptions(db: Session, demo: User, n: int = 14) -> int:
|
|
"""Seed the demo with a handful of catalog channels (the ones with the most videos) so its
|
|
Channel manager isn't empty — gives visitors something to explore and matches the landing-page
|
|
screenshot. Idempotent: clears the demo's subscriptions first, then re-adds the top N."""
|
|
db.execute(delete(Subscription).where(Subscription.user_id == demo.id))
|
|
top = db.execute(
|
|
select(Video.channel_id)
|
|
.where(Video.channel_id.is_not(None))
|
|
.group_by(Video.channel_id)
|
|
.order_by(func.count().desc())
|
|
.limit(n)
|
|
).scalars().all()
|
|
for channel_id in top:
|
|
db.add(Subscription(user_id=demo.id, channel_id=channel_id))
|
|
return len(top)
|
|
|
|
|
|
def reset_demo_state(db: Session, demo: User) -> int:
|
|
"""Wipe the demo account's per-user state (watch/save/hide states, playlists, preferences)
|
|
back to a clean baseline and re-seed a few sample playlists + channel subscriptions. Returns
|
|
the seeded playlist count. Shared by the admin's manual reset button and the scheduled demo
|
|
reset (see scheduler)."""
|
|
db.execute(delete(VideoState).where(VideoState.user_id == demo.id))
|
|
# Playlist items cascade on playlist delete (ondelete="CASCADE").
|
|
db.execute(delete(Playlist).where(Playlist.user_id == demo.id))
|
|
demo.preferences = {"language": "en"}
|
|
db.commit()
|
|
seeded = _seed_demo_playlists(db, demo)
|
|
_seed_demo_subscriptions(db, demo)
|
|
db.commit()
|
|
return seeded
|