refactor: R12 S3 — invert scheduler->routes, publish cross-module privates

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.
This commit is contained in:
2026-08-11 22:58:10 +02:00
parent ac6383e92c
commit 51463b226e
16 changed files with 153 additions and 148 deletions
+4 -4
View File
@@ -91,7 +91,7 @@ def feed_columns(user: User, state) -> list:
]
def _serialize(row) -> dict:
def serialize(row) -> dict:
return {
"id": row.id,
"title": row.title,
@@ -258,7 +258,7 @@ def _filtered_query(
# channel searches work. `rank_expr` (weight-aware ts_rank) drives the "relevance" sort.
rank_expr = None
if q:
ts_str = _to_tsquery_str(q)
ts_str = to_tsquery_str(q)
if ts_str:
tsq = func.to_tsquery("public.unaccent_simple", ts_str)
query = query.where(
@@ -341,7 +341,7 @@ def _filtered_query(
return query, rank_expr
def _to_tsquery_str(q: str) -> str | None:
def to_tsquery_str(q: str) -> str | None:
"""Turn free-text into a to_tsquery() string: word runs ANDed together, the LAST word
prefix-matched (`:*`) for as-you-type search — e.g. "ti amo magyar" → "ti & amo & magyar:*".
Only `\\w` runs containing an alphanumeric are kept, so no tsquery operators can leak in
@@ -512,7 +512,7 @@ def get_feed(
has_more = len(rows) > limit
page = rows[:limit]
return {
"items": [_serialize(r) for r in page],
"items": [serialize(r) for r in page],
"next_cursor": _encode_cursor(keys, page[-1]) if (has_more and page) else None,
"has_more": has_more,
"limit": limit,