Files
yourpart3/backend/scripts/dedupe-bisaya-family-courses.js
Torsten Schulz (local) 68ac5ec281
All checks were successful
Deploy to production / deploy (push) Successful in 2m51s
feat(bisaya-course): add new lesson and update course creation logic
- Introduced a new lesson titled "Zahlen & Preise" to enhance the numerical curriculum in Bisaya.
- Updated the course creation logic to check for existing "Familien"-Bisaya courses, ensuring idempotency in course creation.
- Enhanced the lesson didactics by mapping legacy titles to current lesson keys, improving data consistency.
- Adjusted the course generation script to limit Bisaya courses to German-speaking learners only, streamlining course offerings.
2026-04-16 22:16:23 +02:00

140 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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);
});