#!/usr/bin/env node /** * Entfernt doppelte „Bisaya für Familien - Alltag & Stabilisierung“-Kurse und behält * den Kurs mit der kleinsten ID (üblicherweise der erste / in der UI verlinkte). * * Standard: Nur Anzeige (Dry-Run). Löschen nur mit: * VOCAB_DEDUPE_BISAYA_FAMILY_APPLY=1 node backend/scripts/dedupe-bisaya-family-courses.js */ import { sequelize } from '../utils/sequelize.js'; const CANONICAL_TITLE = 'Bisaya für Familien - Alltag & Stabilisierung'; async function deleteCourseCascade(courseId, transaction) { const lessonRows = await sequelize.query( `SELECT id FROM community.vocab_course_lesson WHERE course_id = :cid`, { replacements: { cid: courseId }, transaction, type: sequelize.QueryTypes.SELECT } ); const lessonIds = lessonRows.map((r) => r.id); if (lessonIds.length > 0) { const exRows = await sequelize.query( `SELECT id FROM community.vocab_grammar_exercise WHERE lesson_id IN (:lids)`, { replacements: { lids: lessonIds }, transaction, type: sequelize.QueryTypes.SELECT } ); const exerciseIds = exRows.map((r) => r.id); if (exerciseIds.length > 0) { await sequelize.query( `DELETE FROM community.vocab_grammar_exercise_progress WHERE exercise_id IN (:eids)`, { replacements: { eids: exerciseIds }, transaction } ); await sequelize.query(`DELETE FROM community.vocab_grammar_exercise WHERE id IN (:eids)`, { replacements: { eids: exerciseIds }, transaction }); } await sequelize.query( `DELETE FROM community.vocab_course_progress WHERE lesson_id IN (:lids) OR course_id = :cid`, { replacements: { lids: lessonIds, cid: courseId }, transaction } ); await sequelize.query( `DELETE FROM community.vocab_course_lesson WHERE course_id = :cid`, { replacements: { cid: courseId }, transaction } ); } else { await sequelize.query( `DELETE FROM community.vocab_course_progress WHERE course_id = :cid`, { replacements: { cid: courseId }, transaction } ); } await sequelize.query( `DELETE FROM community.vocab_course_enrollment WHERE course_id = :cid`, { replacements: { cid: courseId }, transaction } ); await sequelize.query(`DELETE FROM community.vocab_course WHERE id = :cid`, { replacements: { cid: courseId }, transaction }); } async function main() { const apply = process.env.VOCAB_DEDUPE_BISAYA_FAMILY_APPLY === '1'; await sequelize.authenticate(); const [bisayaLanguage] = await sequelize.query( `SELECT id FROM community.vocab_language WHERE name = 'Bisaya' LIMIT 1`, { type: sequelize.QueryTypes.SELECT } ); if (!bisayaLanguage) { console.error('❌ Bisaya-Sprache nicht gefunden.'); process.exit(1); } const courses = await sequelize.query( `SELECT id, title, owner_user_id AS "ownerUserId", created_at AS "createdAt" FROM community.vocab_course WHERE language_id = :languageId AND title = :title ORDER BY id ASC`, { replacements: { languageId: bisayaLanguage.id, title: CANONICAL_TITLE }, type: sequelize.QueryTypes.SELECT } ); if (courses.length <= 1) { console.log( courses.length === 0 ? `Kein Kurs mit Titel „${CANONICAL_TITLE}“ gefunden.` : `Nur ein Kurs – nichts zu bereinigen (ID ${courses[0].id}).` ); await sequelize.close(); return; } const keep = courses[0]; const remove = courses.slice(1); console.log(`Behalten: Kurs-ID ${keep.id} (älteste ID)\n`); console.log(`${apply ? 'Löschen' : 'Würde löschen'} (${remove.length} Duplikate):`); for (const c of remove) { const stat = await sequelize.query( `SELECT (SELECT COUNT(*)::int FROM community.vocab_course_enrollment WHERE course_id = :cid) AS enroll, (SELECT COUNT(*)::int FROM community.vocab_course_progress WHERE course_id = :cid) AS prog`, { replacements: { cid: c.id }, type: sequelize.QueryTypes.SELECT } ); const row = stat[0]; console.log( ` – ID ${c.id} (owner ${c.ownerUserId}, Enrollments ${row?.enroll ?? '?'}, Fortschrittszeilen ${row?.prog ?? '?'})` ); } if (!apply) { console.log( '\nDry-Run. Zum tatsächlichen Löschen: VOCAB_DEDUPE_BISAYA_FAMILY_APPLY=1 node backend/scripts/dedupe-bisaya-family-courses.js' ); await sequelize.close(); return; } await sequelize.transaction(async (t) => { for (const c of remove) { await deleteCourseCascade(c.id, t); console.log(`🗑️ Kurs ${c.id} entfernt.`); } }); console.log(`\n✅ Fertig. Verbleibender Kurs: ID ${keep.id}`); await sequelize.close(); } main().catch((e) => { console.error('❌ Fehler:', e); sequelize.close(); process.exit(1); });