feat: add A2-B1 course expansion features including listening activities and conversation sessions

- Updated VocabCourseView.vue to display CEFR level for courses.
- Enhanced VocabLessonView.vue with new sections for listening activities and conversation labs, including audio controls and transcript toggling.
- Created migration to add new columns to vocab_course and new tables for conversation sessions and listening activities.
- Implemented models for vocab_conversation_session and vocab_listening_activity.
- Developed scripts to create new A2 and B1 bridge courses with structured lesson plans.
- Documented the A2-B1 expansion plan detailing course structure, learning goals, and assessment criteria.
This commit is contained in:
Torsten Schulz (local)
2026-08-27 15:18:16 +02:00
parent bb719b3c75
commit 3248579529
16 changed files with 680 additions and 12 deletions

View File

@@ -0,0 +1,54 @@
'use strict';
module.exports = {
async up(queryInterface) {
await queryInterface.sequelize.transaction(async (transaction) => {
await queryInterface.sequelize.query(`
ALTER TABLE community.vocab_course
ADD COLUMN IF NOT EXISTS cefr_level TEXT,
ADD COLUMN IF NOT EXISTS course_kind TEXT NOT NULL DEFAULT 'core',
ADD COLUMN IF NOT EXISTS prerequisite_course_id INTEGER NULL
REFERENCES community.vocab_course(id) ON DELETE SET NULL;
CREATE TABLE IF NOT EXISTS community.vocab_conversation_session (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES community."user"(id) ON DELETE CASCADE,
lesson_id INTEGER NOT NULL REFERENCES community.vocab_course_lesson(id) ON DELETE CASCADE,
scenario JSONB NOT NULL DEFAULT '{}'::jsonb,
turns JSONB NOT NULL DEFAULT '[]'::jsonb,
summary JSONB NULL,
status TEXT NOT NULL DEFAULT 'active',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS vocab_conversation_session_user_lesson_idx
ON community.vocab_conversation_session(user_id, lesson_id, updated_at DESC);
CREATE TABLE IF NOT EXISTS community.vocab_listening_activity (
id SERIAL PRIMARY KEY,
lesson_id INTEGER NOT NULL REFERENCES community.vocab_course_lesson(id) ON DELETE CASCADE,
audio_url TEXT NOT NULL,
transcript TEXT NOT NULL,
translation TEXT NULL,
speaker_profile JSONB NOT NULL DEFAULT '{}'::jsonb,
speed TEXT NOT NULL DEFAULT 'natural',
questions JSONB NOT NULL DEFAULT '[]'::jsonb,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS vocab_listening_activity_lesson_idx
ON community.vocab_listening_activity(lesson_id);
`, { transaction });
});
},
async down(queryInterface) {
await queryInterface.sequelize.query(`
DROP TABLE IF EXISTS community.vocab_listening_activity;
DROP TABLE IF EXISTS community.vocab_conversation_session;
ALTER TABLE community.vocab_course
DROP COLUMN IF EXISTS prerequisite_course_id,
DROP COLUMN IF EXISTS course_kind,
DROP COLUMN IF EXISTS cefr_level;
`);
}
};

View File

@@ -0,0 +1,22 @@
'use strict';
/** Repairs the already generated exercise that rendered as a bare blank. */
module.exports = {
async up(queryInterface) {
await queryInterface.sequelize.query(`
UPDATE community.vocab_grammar_exercise AS exercise
SET question_data = '{"type":"gap_fill","text":"{gap} ka padulong?","gaps":1}'::jsonb,
answer_data = '{"type":"gap_fill","answers":["Asa"]}'::jsonb,
explanation = 'Das Kernmuster lautet: "Asa ka padulong?"'
FROM community.vocab_course_lesson AS lesson
WHERE exercise.lesson_id = lesson.id
AND lesson.lesson_number = 46
AND lesson.title = 'Fragen im Alltag vertiefen'
AND exercise.title = 'Fragen im Alltag vertiefen: Kernmuster ergänzen'
AND exercise.question_data->>'text' = '{gap}';
`);
},
async down() {
// The old exercise has no useful learner-visible prompt and is not restored.
}
};