pediatric-ai-scribe-v3/migrations/1780800000000_retire-learning-hub.js
Daniel 025290d64a
Some checks failed
Forgejo Android APK / Root app tests (push) Successful in 47s
Forgejo Docker Build / Root app tests (push) Successful in 45s
Forgejo Android APK / Build signed APK (push) Successful in 2m1s
Forgejo Docker Build / Build Docker image (push) Successful in 9s
Forgejo Docker Build / Deploy to the host (push) Failing after 0s
feat: retire Learning Hub
My Resources generates better slides than Learning Hub ever did — a typed deck
the model fills in, rendered by python-pptx with fit-to-slide text, figures, a
vision review and themes, against Learning Hub's markdown-through-pandoc — and
the articles and quizzes now live in the quiz app. Keeping a second, weaker
generator and a whole CMS beside it was not earning its maintenance.

Removed: three routers, the Learning Hub and Content Manager tabs, their
components and frontend modules, the five database tables, the WebDAV browser,
the content embedding column and its vector index.

Content was exported first — every article as markdown plus a full SQL dump of
all five tables — to ops-backups/learning-hub-export-*. That export is the
restore path; the migration's down() can recreate the shape but never the rows,
and says so.

Two things this simplifies rather than merely deletes:

generated_image_links existed only to record which published content an image
appeared in, and it was the sole reason a generated image could be read by
someone who did not make it. Images are now owner-only — the visibility rule is
one WHERE clause instead of a join across two tables and a published flag.

embeddings.js keeps the model discovery the admin panel uses and loses
searchSimilar and generateContentEmbedding, which queried a table that no longer
exists.

Kept deliberately: Nextcloud connect, disconnect and export, which are how a
generated note reaches a real filesystem and have nothing to do with Learning
Hub; learningRetrieval, which despite its name is the clinical corpus search My
Resources depends on; and the pandoc reference deck, still the fallback when the
python renderer fails, moved from assets/learning to assets/deck now that the
old name misleads.

Tests: four Learning-Hub-only files removed, and the individual cases inside
shared files that asserted its behaviour. Where a test used a Learning endpoint
only as a convenient example — the account-boundary token test, the policy
matrix — it now uses one that still exists, so the property it proves is
unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dv6sqaY6Vq3ChZHMem3cnU
2026-09-12 20:14:20 +02:00

39 lines
2.3 KiB
JavaScript

// Learning Hub is retired. My Resources generates better slides from a typed
// deck, and the articles and quizzes live in the quiz app.
//
// The content was exported before this ran — every article as markdown plus a
// full SQL dump of all five tables — to ops-backups/learning-hub-export-*.
// That export is the restore path; this migration is not reversible in any
// useful sense, because down() can recreate the shape but never the rows.
//
// generated_image_links goes with it: it existed only to say which Learning Hub
// content an image was published in, and it was the sole reason a generated
// image could ever be read by someone who did not make it. Images are now
// owner-only, which is both simpler and stricter.
exports.up = async function (pgm) {
// Order matters only for readability — CASCADE handles the dependencies.
pgm.sql('DROP TABLE IF EXISTS generated_image_links CASCADE');
pgm.sql('DROP TABLE IF EXISTS learning_progress CASCADE');
pgm.sql('DROP TABLE IF EXISTS learning_options CASCADE');
pgm.sql('DROP TABLE IF EXISTS learning_questions CASCADE');
pgm.sql('DROP TABLE IF EXISTS learning_content CASCADE');
pgm.sql('DROP TABLE IF EXISTS learning_categories CASCADE');
// The workflow enum on image jobs. Any rows for the retired workflow go with
// it: those images belonged to Learning Hub content that no longer exists.
pgm.sql("DELETE FROM generated_image_jobs WHERE workflow = 'learning_hub'");
pgm.sql('ALTER TABLE generated_image_jobs DROP CONSTRAINT IF EXISTS generated_image_jobs_workflow_check');
pgm.sql("ALTER TABLE generated_image_jobs ADD CONSTRAINT generated_image_jobs_workflow_check " +
"CHECK (workflow = ANY (ARRAY['clinical_assistant'::text, 'my_resources'::text]))");
// Settings that only ever addressed Learning Hub.
pgm.sql("DELETE FROM app_settings WHERE key LIKE 'learning_hub.%'");
};
exports.down = async function (pgm) {
// The shape, not the content. Restoring the rows means loading the export.
pgm.sql('ALTER TABLE generated_image_jobs DROP CONSTRAINT IF EXISTS generated_image_jobs_workflow_check');
pgm.sql("ALTER TABLE generated_image_jobs ADD CONSTRAINT generated_image_jobs_workflow_check " +
"CHECK (workflow = ANY (ARRAY['clinical_assistant'::text, 'learning_hub'::text, 'my_resources'::text]))");
};