kryten-webqueue 0.47.0__tar.gz → 0.48.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/CHANGELOG.md +13 -0
- kryten_webqueue-0.48.0/Containerfile +23 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/PKG-INFO +4 -1
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/config.example.json +13 -1
- kryten_webqueue-0.48.0/deploy/podman/webqueue/webqueue-app.container +24 -0
- kryten_webqueue-0.48.0/deploy/podman/webqueue/webqueue.network +3 -0
- kryten_webqueue-0.48.0/docs/postgres-cutover.md +94 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/app.py +8 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/__init__.py +27 -15
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/_catalog.py +24 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/_jobs_db.py +28 -0
- kryten_webqueue-0.48.0/kryten_webqueue/catalog/db/engine.py +29 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/schemas/catalog_schema.py +3 -3
- kryten_webqueue-0.48.0/kryten_webqueue/catalog/db/sql/001_initial_schema.sql +463 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/config.py +96 -27
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/jobs/tasks.py +27 -0
- kryten_webqueue-0.48.0/kryten_webqueue/migrate_sqlite_to_pg.py +350 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0/kryten_webqueue}/scripts/split_databases.py +4 -12
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/pyproject.toml +5 -1
- kryten_webqueue-0.48.0/scripts/split_databases.py +13 -0
- kryten_webqueue-0.48.0/tests/test_postgres_config_and_engine.py +77 -0
- kryten_webqueue-0.48.0/tests/test_postgres_pruner.py +131 -0
- kryten_webqueue-0.47.0/kryten_webqueue/scripts/split_databases.py +0 -14
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/.github/workflows/gitleaks.yml +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/.github/workflows/python-publish.yml +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/.github/workflows/release.yml +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/.gitignore +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/.gitleaks.toml +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/.pre-commit-config.yaml +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/AGENT-WORKFLOW-GUIDE.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/AGENTS.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/README.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/deploy/kryten-webqueue.service +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/deploy/nginx-queue.conf +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/DATABASE_ARCHITECTURE_PLAN.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/IMPLEMENTATION_SPEC.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/IMPL_API_GATE.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/IMPL_ECONOMY.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/IMPL_KRYTEN_PY.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/IMPL_ROBOT.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/Kryten-Economy Service Quality Survey Report at v0.13.0.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/Kryten-Webqueue Service Quality Survey Report at v0.27.1.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/Log for incorrect promo insertion problem.txt +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/PLAN_PRESENCE_AND_PROMOS.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/PRD_TMDB_LOCAL_INDEX.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/PRE_PLAN_GAPS.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/PRODUCT_PLAN.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/PUBLIC_API.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/SPEC-Visual-UX-Sprint-August-2026.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/SPEC_ADMIN_ITEM_EDITOR.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/SPEC_CATALOG_ENRICHMENT_PIPELINE.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/SPEC_EMOTE_REHOST.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/SPEC_JOBS_AND_BROWSE.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/SPEC_TMDB_LOCAL_INDEX.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/UX_POLISH_PLAN.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/postgres-migration/PRD-postgres-migration.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/postgres-migration/SPEC-Sortie-1-db-config-and-pool.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/postgres-migration/SPEC-Sortie-2-connection-layer-port.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/postgres-migration/SPEC-Sortie-3-fts5-to-tsvector.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/postgres-migration/SPEC-Sortie-4-tmdb-index-database.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/postgres-migration/SPEC-Sortie-5-etl-migration.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/postgres-migration/SPEC-Sortie-6-tests-cutover-release.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/sqlite-domain-separation/PRD-sqlite-domain-separation.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/sqlite-domain-separation/SPEC-Sortie-1-schema-partitioning-and-config.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/sqlite-domain-separation/SPEC-Sortie-2-multi-db-connection-layer.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/sqlite-domain-separation/SPEC-Sortie-3-cross-domain-query-decoupling.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/docs/sqlite-domain-separation/SPEC-Sortie-4-etl-split-script-and-validation.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/__main__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/api_gate/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/api_gate/client.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/auth/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/auth/device_keys.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/auth/otp.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/auth/rate_limit.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/auth/session.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/_base_domain.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/_blackouts.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/_catalog_db.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/_connection.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/_devices.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/_enrichment.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/_feedback.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/_fetch_queue.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/_motd.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/_people.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/_playlists.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/_queue.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/_queue_db.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/_users_db.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/_watchlist.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/schemas/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/schemas/jobs_schema.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/schemas/queue_schema.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/db/schemas/users_schema.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/enrichment/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/enrichment/classify.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/enrichment/normalise.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/enrichment/pipeline.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/enrichment/providers.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/enrichment/report.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/enrichment/steps/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/enrichment/steps/art.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/enrichment/steps/categories.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/enrichment/steps/identify.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/enrichment/steps/meta.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/enrichment/steps/sync.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/enrichment/steps/tags.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/enrichment/steps/title.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/images.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/mediacms.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/sync.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/tmdb_index/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/tmdb_index/_schema.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/tmdb_index/_textmatch.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/tmdb_index/_ttscrape.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/tmdb_index/builder.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/tmdb_index/coverage.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/catalog/tmdb_index/index.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/integrations/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/integrations/cmsutils/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/integrations/cmsutils/_common.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/integrations/cmsutils/blackout_scan.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/integrations/cmsutils/enrichmeta.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/integrations/cmsutils/enrichtitles.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/integrations/cmsutils/enrichtv.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/integrations/cmsutils/fetchurls.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/integrations/cmsutils/motdposters.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/integrations/cmsutils/playedmovies.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/integrations/ytpipe/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/integrations/ytpipe/downloader.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/jobs/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/jobs/fetchurls_auth.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/jobs/job_scheduler.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/jobs/log_capture.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/jobs/manager.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/jobs/motd_publish.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/jobs/rehost_emotes.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/logging_config.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/motd/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/motd/builder.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/motd/render.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/playlists/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/playlists/bulk_add.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/playlists/fire.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/playlists/importer.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/playlists/ordering.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/playlists/scheduler.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/promos/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/promos/director.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/queue/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/queue/completion.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/queue/ordering.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/queue/poller.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/queue/presence.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/queue/race_poller.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/queue/shadow.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/routes/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/routes/admin_catalog.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/routes/admin_feedback.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/routes/admin_job_schedules.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/routes/admin_jobs.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/routes/admin_moderation.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/routes/admin_motd.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/routes/admin_playlists.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/routes/admin_promos.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/routes/admin_queue.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/routes/admin_schedules.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/routes/auth.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/routes/catalog.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/routes/devices.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/routes/feedback.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/routes/pages.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/routes/public_api.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/routes/queue.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/routes/user.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/scripts/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/static/css/main.css +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/static/js/admin-motd.js +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/static/js/admin-playlists.js +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/static/js/admin-promos.js +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/static/js/admin-queue-mgmt.js +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/static/js/admin-schedules.js +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/static/js/admin.js +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/static/js/keybindings.js +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/static/js/main.js +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/templates/admin/index.html +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/templates/admin/motd.html +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/templates/admin/playlists.html +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/templates/admin/promos.html +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/templates/admin/queue_mgmt.html +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/templates/admin/schedules.html +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/templates/auth/login.html +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/templates/base.html +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/templates/catalog/browse.html +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/templates/catalog/edit_item_modal.html +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/templates/catalog/item_detail.html +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/templates/catalog/item_not_found.html +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/templates/feedback/index.html +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/templates/motd/channel_z.html +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/templates/queue/index.html +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/templates/race.html +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/templates/user/dashboard.html +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/templates/user/link_device.html +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/templates/user/my_list.html +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/ws/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/ws/handler.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/kryten_webqueue/ws/manager.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/scripts/AGENTS.md +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/scripts/patch_mediacms_bulk_facets.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/scripts/patch_mediacms_tags.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/scripts/trigger_enrich.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/__init__.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_catalog_blackout.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_catalog_delete.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_classify.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_concurrency_split.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_config_persistence.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_database_config_and_schemas.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_device_linking.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_feedback.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_fetch_queue_pacing.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_fetchurls_sharepoint.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_identify_step.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_job_logs_and_queue_visibility.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_motd_publish.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_multi_db_connection.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_people_facets.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_phase1.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_phase2_jobs.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_phase3_jobs.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_phase4_live_fixes.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_playlist_import.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_presence_refund.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_promo_director.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_promo_pool_exclusion.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_providers.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_queue_announce.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_queue_preview_fallback.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_race_poller.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_recently_played_hide.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_save_results_to_playlist.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_schedule_lock.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_search_facets.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_title_normalization.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_tmdb_index.py +0 -0
- {kryten_webqueue-0.47.0 → kryten_webqueue-0.48.0}/tests/test_watchlist.py +0 -0
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.48.0] - 2026-09-23
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **PostgreSQL & Chandra-1 Podman Migration Foundations (`postgres-migration`)**:
|
|
8
|
+
- Added PostgreSQL backend support and configuration in `kryten_webqueue/config.py` with `PostgresConfig` and `DatabaseConfig(backend="sqlite"|"postgres")`.
|
|
9
|
+
- Added SQLAlchemy 2.0 async engine and session factory builder in `kryten_webqueue/catalog/db/engine.py` (`create_pg_engine`) utilizing `asyncpg` with connection pooling (`pool_size=10`, `max_overflow=20`, `pool_pre_ping=True`).
|
|
10
|
+
- Added schema DDL `kryten_webqueue/catalog/db/sql/001_initial_schema.sql` defining 40 tables across 5 logical schemas (`catalog`, `queue`, `jobs`, `users`, `tmdb`), English `tsvector` generated search vector, and `pg_trgm` fuzzy trigram matching.
|
|
11
|
+
- Implemented automated 30-day job run log pruning task (`job_log_prune_job`) in `kryten_webqueue/jobs/tasks.py` and `kryten_webqueue/catalog/db/_jobs_db.py` with strict exemptions for financial, chat, feedback, and audit history.
|
|
12
|
+
- Implemented one-shot ETL streaming script `kryten_webqueue/migrate_sqlite_to_pg.py` (`kryten-webqueue-migrate-pg`) with batched row streaming, identity sequence synchronization (`setval`), type normalization, and verification reporting.
|
|
13
|
+
- Added rootful Podman Quadlet specifications (`deploy/podman/webqueue/webqueue.network` and `webqueue-app.container`) and Containerfile for deployment on `chandra-1`.
|
|
14
|
+
- Added comprehensive operational cutover runbook `docs/postgres-cutover.md`.
|
|
15
|
+
|
|
3
16
|
## [0.47.0] - 2026-09-23
|
|
4
17
|
|
|
5
18
|
### Added
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
FROM python:3.12-slim
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
RUN apt-get update \
|
|
6
|
+
&& apt-get install -y --no-install-recommends build-essential ffmpeg curl \
|
|
7
|
+
&& rm -rf /var/lib/apt/lists/* \
|
|
8
|
+
&& useradd --system --create-home --uid 1001 kryten
|
|
9
|
+
|
|
10
|
+
COPY . /tmp/kryten-webqueue
|
|
11
|
+
|
|
12
|
+
RUN pip install --no-cache-dir --upgrade pip \
|
|
13
|
+
&& pip install --no-cache-dir /tmp/kryten-webqueue
|
|
14
|
+
|
|
15
|
+
RUN mkdir -p /etc/kryten-webqueue /var/lib/kryten-webqueue /var/log/kryten-webqueue \
|
|
16
|
+
&& chown -R kryten:kryten /etc/kryten-webqueue /var/lib/kryten-webqueue /var/log/kryten-webqueue
|
|
17
|
+
|
|
18
|
+
USER kryten
|
|
19
|
+
|
|
20
|
+
EXPOSE 2010
|
|
21
|
+
|
|
22
|
+
ENTRYPOINT ["kryten-webqueue"]
|
|
23
|
+
CMD ["--config", "/etc/kryten-webqueue/config.json"]
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: kryten-webqueue
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.48.0
|
|
4
4
|
Summary: Netflix/Tubi-style catalog browser and pay-to-play queue management for CyTube
|
|
5
5
|
Author: grobertson
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Requires-Python: >=3.12
|
|
8
8
|
Requires-Dist: aiosqlite>=0.20
|
|
9
9
|
Requires-Dist: apscheduler>=3.10
|
|
10
|
+
Requires-Dist: asyncpg>=0.29.0
|
|
10
11
|
Requires-Dist: fastapi>=0.115
|
|
12
|
+
Requires-Dist: greenlet>=3.0
|
|
11
13
|
Requires-Dist: httpx>=0.27
|
|
12
14
|
Requires-Dist: jinja2>=3.1
|
|
13
15
|
Requires-Dist: msal>=1.28
|
|
@@ -21,6 +23,7 @@ Requires-Dist: python-multipart>=0.0.9
|
|
|
21
23
|
Requires-Dist: python-slugify>=8.0
|
|
22
24
|
Requires-Dist: pyyaml>=6.0
|
|
23
25
|
Requires-Dist: requests>=2.31
|
|
26
|
+
Requires-Dist: sqlalchemy>=2.0.30
|
|
24
27
|
Requires-Dist: uvicorn[standard]>=0.30
|
|
25
28
|
Requires-Dist: websockets>=12.0
|
|
26
29
|
Requires-Dist: yt-dlp>=2024.1
|
|
@@ -63,13 +63,25 @@
|
|
|
63
63
|
},
|
|
64
64
|
|
|
65
65
|
"database": {
|
|
66
|
+
"backend": "sqlite",
|
|
66
67
|
"layout": "monolith",
|
|
67
68
|
"db_path": "/var/lib/kryten-webqueue/webqueue.db",
|
|
68
69
|
"data_dir": "/var/lib/kryten-webqueue/data",
|
|
69
70
|
"catalog_db_path": null,
|
|
70
71
|
"queue_db_path": null,
|
|
71
72
|
"jobs_db_path": null,
|
|
72
|
-
"users_db_path": null
|
|
73
|
+
"users_db_path": null,
|
|
74
|
+
"postgres": {
|
|
75
|
+
"host": "host.containers.internal",
|
|
76
|
+
"port": 5432,
|
|
77
|
+
"user": "kryten",
|
|
78
|
+
"dbname": "webqueue",
|
|
79
|
+
"password_env": "KRYTEN_WEBQUEUE_PG_PASSWORD",
|
|
80
|
+
"dsn_env": null,
|
|
81
|
+
"dsn": null,
|
|
82
|
+
"pool_size": 10,
|
|
83
|
+
"max_overflow": 20
|
|
84
|
+
}
|
|
73
85
|
},
|
|
74
86
|
"db_path": "/var/lib/kryten-webqueue/webqueue.db",
|
|
75
87
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[Unit]
|
|
2
|
+
Description=kryten-webqueue - CyTube Queue Management (FastAPI app)
|
|
3
|
+
After=network-online.target
|
|
4
|
+
|
|
5
|
+
[Container]
|
|
6
|
+
ContainerName=webqueue-app
|
|
7
|
+
Image=localhost/kryten-webqueue:latest
|
|
8
|
+
Network=podman-webqueue
|
|
9
|
+
PublishPort=2010:2010
|
|
10
|
+
AddHost=host.containers.internal:host-gateway
|
|
11
|
+
|
|
12
|
+
EnvironmentFile=/etc/kryten/webqueue/webqueue.env
|
|
13
|
+
Volume=/etc/kryten/webqueue/config.json:/etc/kryten-webqueue/config.json:ro,z
|
|
14
|
+
Volume=/var/lib/kryten/media:/var/lib/kryten-webqueue:Z
|
|
15
|
+
Volume=/var/log/kryten/webqueue:/var/log/kryten-webqueue:Z
|
|
16
|
+
|
|
17
|
+
Exec=uvicorn kryten_webqueue.app:app --host 0.0.0.0 --port 2010
|
|
18
|
+
|
|
19
|
+
[Service]
|
|
20
|
+
Restart=always
|
|
21
|
+
RestartSec=5
|
|
22
|
+
|
|
23
|
+
[Install]
|
|
24
|
+
WantedBy=multi-user.target default.target
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# PostgreSQL Cutover Runbook: Kryten-WebQueue
|
|
2
|
+
|
|
3
|
+
**Target Version**: `0.48.0`
|
|
4
|
+
**Host Target**: `chandra-1` (PostgreSQL 16 + Podman Quadlet)
|
|
5
|
+
**Source Host**: `grindhouse.local` (pipx systemd / SQLite partitioned data)
|
|
6
|
+
**Parent Plan**: [DATABASE_ARCHITECTURE_PLAN.md](DATABASE_ARCHITECTURE_PLAN.md)
|
|
7
|
+
**PRD**: [postgres-migration/PRD-postgres-migration.md](postgres-migration/PRD-postgres-migration.md)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 1. Pre-Flight Checklist
|
|
12
|
+
|
|
13
|
+
- [ ] `chandra-1` has PostgreSQL 16+ active and `pg_trgm` extension installed.
|
|
14
|
+
- [ ] Database `webqueue` exists with owner `kryten`.
|
|
15
|
+
- [ ] Schema `001_initial_schema.sql` applied cleanly across `catalog`, `queue`, `jobs`, `users`, `tmdb`.
|
|
16
|
+
- [ ] Privileges granted to `kryten`:
|
|
17
|
+
```sql
|
|
18
|
+
GRANT USAGE, CREATE ON SCHEMA catalog, queue, jobs, users, tmdb TO kryten;
|
|
19
|
+
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA catalog, queue, jobs, users, tmdb, public TO kryten;
|
|
20
|
+
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA catalog, queue, jobs, users, tmdb, public TO kryten;
|
|
21
|
+
ALTER DEFAULT PRIVILEGES IN SCHEMA catalog, queue, jobs, users, tmdb GRANT ALL ON TABLES TO kryten;
|
|
22
|
+
ALTER DEFAULT PRIVILEGES IN SCHEMA catalog, queue, jobs, users, tmdb GRANT ALL ON SEQUENCES TO kryten;
|
|
23
|
+
```
|
|
24
|
+
- [ ] Podman image built on `chandra-1`:
|
|
25
|
+
```bash
|
|
26
|
+
sudo podman build -f Containerfile -t localhost/kryten-webqueue:latest .
|
|
27
|
+
```
|
|
28
|
+
- [ ] Environment file `/etc/kryten/webqueue/webqueue.env` created on `chandra-1`:
|
|
29
|
+
```env
|
|
30
|
+
KRYTEN_WEBQUEUE_PG_PASSWORD=kryten_secret_password
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 2. Maintenance Window Cutover (Step-by-Step)
|
|
36
|
+
|
|
37
|
+
### Step 1: Stop Source Service & Freeze Writes
|
|
38
|
+
On `grindhouse.local`:
|
|
39
|
+
```bash
|
|
40
|
+
sudo systemctl stop kryten-webqueue.service
|
|
41
|
+
sudo systemctl status kryten-webqueue.service --no-pager
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Step 2: Checkpoint SQLite WAL & Create Final Snapshot
|
|
45
|
+
On `grindhouse.local`:
|
|
46
|
+
```bash
|
|
47
|
+
SNAPSHOT_DIR="/var/lib/kryten-webqueue/backups/cutover_$(date +%Y%m%d_%H%M%S)"
|
|
48
|
+
sudo mkdir -p "$SNAPSHOT_DIR"
|
|
49
|
+
|
|
50
|
+
for db in catalog queue jobs users; do
|
|
51
|
+
sudo sqlite3 "/var/lib/kryten-webqueue/data/$db.sqlite3" "PRAGMA wal_checkpoint(TRUNCATE);"
|
|
52
|
+
done
|
|
53
|
+
|
|
54
|
+
sudo cp -av /var/lib/kryten-webqueue/data "$SNAPSHOT_DIR/"
|
|
55
|
+
sudo cp -av /etc/kryten-webqueue/config.json "$SNAPSHOT_DIR/"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Step 3: Stream SQLite Data to PostgreSQL on Chandra-1
|
|
59
|
+
Run the ETL migration script with `--verify`:
|
|
60
|
+
```bash
|
|
61
|
+
sudo -u kryten /home/kryten/.local/pipx/venvs/kryten-webqueue/bin/python -m kryten_webqueue.migrate_sqlite_to_pg \
|
|
62
|
+
--data-dir /var/lib/kryten-webqueue/data \
|
|
63
|
+
--pg-dsn "postgresql://kryten:kryten_secret_password@chandra-1.local:5432/webqueue"
|
|
64
|
+
```
|
|
65
|
+
Verify `postgres_migration_report.json` indicates `"status": "success"` and 0 errors.
|
|
66
|
+
|
|
67
|
+
### Step 4: Transfer Media & Image Assets to Chandra-1
|
|
68
|
+
Sync cover art images to `/var/lib/kryten/media`:
|
|
69
|
+
```bash
|
|
70
|
+
rsync -avz /var/lib/kryten-webqueue/images/ groberts@chandra-1.local:/var/lib/kryten/media/images/
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Step 5: Start Podman Quadlet on Chandra-1
|
|
74
|
+
On `chandra-1`:
|
|
75
|
+
```bash
|
|
76
|
+
sudo cp deploy/podman/webqueue/webqueue.network /etc/containers/systemd/
|
|
77
|
+
sudo cp deploy/podman/webqueue/webqueue-app.container /etc/containers/systemd/
|
|
78
|
+
sudo systemctl daemon-reload
|
|
79
|
+
sudo systemctl start webqueue-app.service
|
|
80
|
+
sudo systemctl status webqueue-app.service --no-pager
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Step 6: Smoke Testing & Health Verification
|
|
84
|
+
```bash
|
|
85
|
+
curl -I http://127.0.0.1:2010/auth/login
|
|
86
|
+
curl -s http://127.0.0.1:2010/queue/next-schedule
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## 3. Rollback & Forward Repair Policy
|
|
92
|
+
|
|
93
|
+
- **Pre-Cutover Failure**: If ETL validation fails or container startup fails before DNS/traffic cutover, immediately restart `kryten-webqueue.service` on `grindhouse.local`. SQLite databases remain untouched and consistent.
|
|
94
|
+
- **Post-Cutover Failure**: Once traffic is switched and PostgreSQL accepts writes (new spend requests, logins, completions), recovery is a **forward repair in PostgreSQL**. The retained SQLite snapshot is preserved read-only for forensics.
|
|
@@ -111,6 +111,7 @@ async def lifespan(app: FastAPI):
|
|
|
111
111
|
motd_posters_job,
|
|
112
112
|
tmdb_index_refresh_job,
|
|
113
113
|
tmdb_coverage_report_job,
|
|
114
|
+
job_log_prune_job,
|
|
114
115
|
CATALOG_ENRICH_SCHEMA,
|
|
115
116
|
CATALOG_BLACKOUT_SCHEMA,
|
|
116
117
|
DEVICE_KEY_BAN_RECONCILE_SCHEMA,
|
|
@@ -121,6 +122,7 @@ async def lifespan(app: FastAPI):
|
|
|
121
122
|
MOTD_POSTERS_SCHEMA,
|
|
122
123
|
TMDB_INDEX_REFRESH_SCHEMA,
|
|
123
124
|
TMDB_COVERAGE_REPORT_SCHEMA,
|
|
125
|
+
JOB_LOG_PRUNE_SCHEMA,
|
|
124
126
|
)
|
|
125
127
|
|
|
126
128
|
job_manager.register(
|
|
@@ -168,6 +170,12 @@ async def lifespan(app: FastAPI):
|
|
|
168
170
|
label="MOTD Posters (weekend poster grid)",
|
|
169
171
|
schema=MOTD_POSTERS_SCHEMA,
|
|
170
172
|
)
|
|
173
|
+
job_manager.register(
|
|
174
|
+
"job_log_prune",
|
|
175
|
+
job_log_prune_job,
|
|
176
|
+
label="Job Run Log Pruner (30-day retention)",
|
|
177
|
+
schema=JOB_LOG_PRUNE_SCHEMA,
|
|
178
|
+
)
|
|
171
179
|
from .jobs.motd_publish import motd_publish_job, MOTD_PUBLISH_SCHEMA
|
|
172
180
|
|
|
173
181
|
job_manager.register(
|
|
@@ -107,17 +107,17 @@ _DOMAIN_METHOD_MAP: dict[str, str] = {
|
|
|
107
107
|
"get_job_schedule": "jobs",
|
|
108
108
|
"upsert_job_schedule": "jobs",
|
|
109
109
|
"delete_job_schedule": "jobs",
|
|
110
|
-
"
|
|
111
|
-
"
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
"
|
|
110
|
+
"prune_job_run_logs": "jobs",
|
|
111
|
+
"enqueue_fetch": "jobs",
|
|
112
|
+
"claim_next_fetch_item": "jobs",
|
|
113
|
+
"finish_fetch_item": "jobs",
|
|
114
|
+
"requeue_fetch_item": "jobs",
|
|
115
|
+
"requeue_fetch_item_for_retry": "jobs",
|
|
116
|
+
"reset_running_fetch_items": "jobs",
|
|
117
|
+
"get_fetch_queue": "jobs",
|
|
118
|
+
"count_fetch_queue": "jobs",
|
|
119
|
+
"count_fetch_queue_pending": "jobs",
|
|
120
|
+
"delete_fetch_queue_item": "jobs",
|
|
121
121
|
# users
|
|
122
122
|
"watchlist_add": "users",
|
|
123
123
|
"watchlist_remove": "users",
|
|
@@ -303,10 +303,22 @@ class Database(
|
|
|
303
303
|
except AttributeError:
|
|
304
304
|
layout = "monolith"
|
|
305
305
|
|
|
306
|
-
if layout == "partitioned"
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
306
|
+
if layout == "partitioned":
|
|
307
|
+
if name in Database.__dict__ or name.startswith("_"):
|
|
308
|
+
return object.__getattribute__(self, name)
|
|
309
|
+
|
|
310
|
+
if name in _DOMAIN_METHOD_MAP:
|
|
311
|
+
domain_name = _DOMAIN_METHOD_MAP[name]
|
|
312
|
+
domain_obj = object.__getattribute__(self, domain_name)
|
|
313
|
+
return getattr(domain_obj, name)
|
|
314
|
+
|
|
315
|
+
for domain_name in ("jobs", "queue", "users", "catalog"):
|
|
316
|
+
try:
|
|
317
|
+
domain_obj = object.__getattribute__(self, domain_name)
|
|
318
|
+
if hasattr(domain_obj, name) and not hasattr(_DBBase, name):
|
|
319
|
+
return getattr(domain_obj, name)
|
|
320
|
+
except AttributeError:
|
|
321
|
+
pass
|
|
310
322
|
|
|
311
323
|
return object.__getattribute__(self, name)
|
|
312
324
|
|
|
@@ -1331,6 +1331,30 @@ class _CatalogMixin:
|
|
|
1331
1331
|
async def delete_job_schedule(self, job_name: str) -> None:
|
|
1332
1332
|
await self._execute("DELETE FROM job_schedules WHERE job_name=?", [job_name])
|
|
1333
1333
|
|
|
1334
|
+
async def prune_job_run_logs(
|
|
1335
|
+
self, retention_days: int = 30, batch_size: int = 5000
|
|
1336
|
+
) -> int:
|
|
1337
|
+
total_deleted = 0
|
|
1338
|
+
cutoff = f"-{int(retention_days)} days"
|
|
1339
|
+
while True:
|
|
1340
|
+
cursor = await self._db.execute(
|
|
1341
|
+
"""
|
|
1342
|
+
DELETE FROM job_run_logs
|
|
1343
|
+
WHERE id IN (
|
|
1344
|
+
SELECT id FROM job_run_logs
|
|
1345
|
+
WHERE logged_at < datetime('now', ?)
|
|
1346
|
+
LIMIT ?
|
|
1347
|
+
)
|
|
1348
|
+
""",
|
|
1349
|
+
[cutoff, batch_size],
|
|
1350
|
+
)
|
|
1351
|
+
await self._db.commit()
|
|
1352
|
+
count = cursor.rowcount or 0
|
|
1353
|
+
total_deleted += count
|
|
1354
|
+
if count < batch_size:
|
|
1355
|
+
break
|
|
1356
|
+
return total_deleted
|
|
1357
|
+
|
|
1334
1358
|
# --- OTP ---
|
|
1335
1359
|
|
|
1336
1360
|
async def store_otp(self, username: str, code: str, expires_at: str):
|
|
@@ -121,3 +121,31 @@ class _JobsDB(_FetchQueueMixin, _DomainDB):
|
|
|
121
121
|
|
|
122
122
|
async def delete_job_schedule(self, job_name: str) -> None:
|
|
123
123
|
await self._execute("DELETE FROM job_schedules WHERE job_name=?", [job_name])
|
|
124
|
+
|
|
125
|
+
async def prune_job_run_logs(
|
|
126
|
+
self, retention_days: int = 30, batch_size: int = 5000
|
|
127
|
+
) -> int:
|
|
128
|
+
"""Prune job_run_logs older than retention_days in bounded batches.
|
|
129
|
+
|
|
130
|
+
Strictly touches ONLY job_run_logs.
|
|
131
|
+
"""
|
|
132
|
+
total_deleted = 0
|
|
133
|
+
cutoff = f"-{int(retention_days)} days"
|
|
134
|
+
while True:
|
|
135
|
+
cursor = await self._db.execute(
|
|
136
|
+
"""
|
|
137
|
+
DELETE FROM job_run_logs
|
|
138
|
+
WHERE id IN (
|
|
139
|
+
SELECT id FROM job_run_logs
|
|
140
|
+
WHERE logged_at < datetime('now', ?)
|
|
141
|
+
LIMIT ?
|
|
142
|
+
)
|
|
143
|
+
""",
|
|
144
|
+
[cutoff, batch_size],
|
|
145
|
+
)
|
|
146
|
+
await self._db.commit()
|
|
147
|
+
count = cursor.rowcount or 0
|
|
148
|
+
total_deleted += count
|
|
149
|
+
if count < batch_size:
|
|
150
|
+
break
|
|
151
|
+
return total_deleted
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""SQLAlchemy 2.0 async engine and session factory for PostgreSQL."""
|
|
2
|
+
|
|
3
|
+
from sqlalchemy.ext.asyncio import (
|
|
4
|
+
AsyncEngine,
|
|
5
|
+
AsyncSession,
|
|
6
|
+
async_sessionmaker,
|
|
7
|
+
create_async_engine,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from ...config import PostgresConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def create_pg_engine(
|
|
14
|
+
config: PostgresConfig,
|
|
15
|
+
) -> tuple[AsyncEngine, async_sessionmaker[AsyncSession]]:
|
|
16
|
+
"""Create a SQLAlchemy 2.0 async engine and sessionmaker from PostgresConfig."""
|
|
17
|
+
url = config.get_async_url()
|
|
18
|
+
engine = create_async_engine(
|
|
19
|
+
url,
|
|
20
|
+
pool_size=config.pool_size,
|
|
21
|
+
max_overflow=config.max_overflow,
|
|
22
|
+
pool_pre_ping=True,
|
|
23
|
+
)
|
|
24
|
+
session_factory = async_sessionmaker(
|
|
25
|
+
bind=engine,
|
|
26
|
+
class_=AsyncSession,
|
|
27
|
+
expire_on_commit=False,
|
|
28
|
+
)
|
|
29
|
+
return engine, session_factory
|
|
@@ -78,7 +78,7 @@ CATALOG_MIGRATIONS: list[str] = [
|
|
|
78
78
|
CREATE INDEX IF NOT EXISTS idx_people_name ON people(name);
|
|
79
79
|
|
|
80
80
|
CREATE TABLE IF NOT EXISTS catalog_people (
|
|
81
|
-
friendly_token TEXT NOT NULL
|
|
81
|
+
friendly_token TEXT NOT NULL,
|
|
82
82
|
person_id INTEGER NOT NULL REFERENCES people(id) ON DELETE CASCADE,
|
|
83
83
|
role TEXT NOT NULL,
|
|
84
84
|
position INTEGER NOT NULL DEFAULT 0,
|
|
@@ -93,14 +93,14 @@ CATALOG_MIGRATIONS: list[str] = [
|
|
|
93
93
|
);
|
|
94
94
|
|
|
95
95
|
CREATE TABLE IF NOT EXISTS catalog_studios (
|
|
96
|
-
friendly_token TEXT NOT NULL
|
|
96
|
+
friendly_token TEXT NOT NULL,
|
|
97
97
|
studio_id INTEGER NOT NULL REFERENCES studios(id) ON DELETE CASCADE,
|
|
98
98
|
PRIMARY KEY (friendly_token, studio_id)
|
|
99
99
|
);
|
|
100
100
|
CREATE INDEX IF NOT EXISTS idx_catalog_studios_token ON catalog_studios(friendly_token);
|
|
101
101
|
|
|
102
102
|
CREATE TABLE IF NOT EXISTS item_enrichment_state (
|
|
103
|
-
friendly_token TEXT PRIMARY KEY
|
|
103
|
+
friendly_token TEXT PRIMARY KEY,
|
|
104
104
|
content_type TEXT,
|
|
105
105
|
hosted_show TEXT,
|
|
106
106
|
lookup_title TEXT,
|