feat: clone course (#582)

* feat: clone course work

* feat: clone and delete a course

* feat: clone and delete a course

* feat: clean up

* feat: attending to reviews

* feat: attended to revies
This commit is contained in:
Nwosu Emmanuel
2025-10-29 09:57:29 +01:00
committed by GitHub
parent f8271d850f
commit 8efffcb7f6
27 changed files with 900 additions and 382 deletions
@@ -1,256 +0,0 @@
import type { Course, Lesson, Profile } from '$lib/utils/types';
import type { PostgrestError } from '@supabase/supabase-js';
// @ts-ignore
import { QUESTION_TYPE } from '$lib/components/Question/constants';
// @ts-ignore
import { ROLE } from '$lib/utils/constants/roles';
// @ts-ignore
import { addGroupMember } from './index';
import { get } from 'svelte/store';
// @ts-ignore
import { profile } from '$lib/utils/store/user';
import { supabase } from '$lib/utils/functions/supabase';
type GetCourseResponse = {
data: Course;
error: PostgrestError | null;
};
async function fetchCourseData(courseId: string): Promise<GetCourseResponse> {
const { data, error } = await supabase
.from('course')
.select(
`
*,
lessons:lesson(
*,
exercise:id(
id, title, description, due_by,
questions:question(
*,
options:option(*),
question_type:question_type_id(id, label)
)
)
)
`
)
.match({ id: courseId })
.single();
return { data, error };
}
async function cloneGroupAndBasicCourse(
course: Course,
newTitle: string
): Promise<{ newCourse: Course }> {
const { description } = course;
const { data: newGroup } = await supabase
.from('group')
.insert({ name: newTitle, description })
.select();
console.log(`newGroup`, newGroup);
if (!Array.isArray(newGroup)) {
throw new Error('Group wasnt created successfully');
}
const { id: group_id } = newGroup[0];
// 2. Create course with group_id
const { data: newCourse } = await supabase
.from('course')
.insert({
...course,
title: newTitle,
description,
group_id,
lessons: undefined,
id: undefined,
updated_at: undefined,
created_at: undefined
})
.select();
console.log(`newCourse`, newCourse);
// 3. Add group members
const profileStoreValue: Profile = get(profile);
// Get profile of author and add as member
await addGroupMember({
profile_id: profileStoreValue.id,
email: profileStoreValue.email,
group_id,
role_id: ROLE.TUTOR
});
console.log('profileStoreValue', profileStoreValue);
return {
newCourse: newCourse?.[0]
};
}
async function cloneLessons(lessons: Lesson[], courseId: Course['id']) {
const clonedLessons = lessons
.sort((a, b) => new Date(a.created_at || '').getTime() - new Date(b.created_at || '').getTime())
.map((lesson, index) => ({
call_url: lesson.call_url,
course_id: courseId,
is_unlocked: lesson.is_unlocked,
lesson_at: new Date(),
note: lesson.note,
order: lesson.order || index + 1,
public: lesson.public,
slide_url: lesson.slide_url,
title: lesson.title,
videos: lesson.videos
}));
const { data } = await supabase.from('lesson').insert(clonedLessons).select();
const newLessons = data?.map((dataItem, dataIndex) => {
return {
...dataItem,
// @ts-ignore
exercise: lessons[dataIndex].exercise
};
});
console.log('newLessons', newLessons);
return { newLessons };
}
async function cloneExercises(lessons: Lesson[]) {
console.log('lessons', lessons);
// Lessons
for (const lesson of lessons) {
// @ts-ignore
const { id: lessonId, exercise = [] } = lesson;
// Exercises
for (const exerciseItem of exercise) {
const { title, description, questions = [] } = exerciseItem;
// Insert
const { data } = await supabase
.from('exercise')
.insert({
description,
due_by: new Date(),
lesson_id: lessonId,
title
})
.select();
const newExercise = Array.isArray(data) ? data[0] : data;
console.log('newExercise', newExercise);
// Questions
for (const question of questions) {
const { options = [] } = question;
// Insert
const { data } = await supabase
.from('question')
.insert({
name: question.name,
title: question.title,
points: question.points,
order: question.order,
question_type_id: question.question_type_id,
exercise_id: newExercise.id
})
.select();
const newQuestion = Array.isArray(data) ? data[0] : data;
console.log('newQuestion', newQuestion);
// Options: Don't map options for 'Paragraph' questions
if (QUESTION_TYPE.TEXTAREA !== newQuestion.question_type_id) {
// @ts-ignore
const newOptions = options.map((option) => ({
value: option.value,
label: option.label,
is_correct: option.is_correct,
question_id: newQuestion.id
}));
// Insert[Batch]
await supabase.from('option').insert(newOptions).select();
}
}
}
}
return true;
}
export async function cloneCourse(courseId: string, newTitle: string): Promise<Course> {
const { data: course, error } = await fetchCourseData(courseId);
console.log(`course`, course);
if (error) {
console.error('Error when cloning', error);
throw error;
}
/**
* 1. Copy course table
* 2. Copy lessons table (NOTE: Copy sections table here)
* 3. Copy exercises table
* 4. Copy questions table
* 5. Copy options table
*/
// tables: group, groupmember, course(group_id)
const { newCourse } = await cloneGroupAndBasicCourse(course, newTitle);
console.log('returned -> newCourse', newCourse);
// @ts-ignore
const { newLessons } = await cloneLessons(course.lessons, newCourse.id);
console.log('returned -> newLessons', newLessons);
const cloneExercisesRes = await cloneExercises(newLessons || []);
console.log('returned -> cloneExercisesRes', cloneExercisesRes);
return newCourse;
}
/*
1. Click on 3 dots to clone course
2. Show a modal with a new name "Copy of previous course name"
-> Note the following information will be copied
3. Things to copy
Lessons
Exercises - questions, options, everything basically.
Technical approach
- Fetch all the data to the client
- Manually call functions to create
- call a psql function with newname, course_id
- copy all in course table
- copy all in group table by course_id
- add only one group member as admin
- get all lessons in course
- copy each one with unique ids
- get all exercises by course id
- copy exercise
- get questions
- copy questions
- get options
- copy options
*/
@@ -1090,6 +1090,17 @@
"date_created": "Dato oprettet",
"published": "Offentliggjort",
"lessons": "Lektioner"
},
"copy_course": {
"title": "Opret et kursus",
"course_name_label": "Kursusnavn",
"course_name_placeholder": "Dit kursusnavn",
"course_description_label": "Kursusbeskrivelse",
"course_description_placeholder": "Indtast en beskrivelse til dit kursus",
"create_button": "Skabe",
"error": {
"failed_to_clone": "Kunne ikke klone kursus"
}
}
},
"ai": {
@@ -937,6 +937,17 @@
"date_created": "Datum erstellt",
"published": "Veröffentlicht",
"lessons": "Unterricht"
},
"copy_course": {
"title": "Erstellen Sie einen Kurs",
"course_name_label": "Kursname",
"course_name_placeholder": "Ihr Kursname",
"course_description_label": "Kursbeschreibung",
"course_description_placeholder": "Geben Sie eine Beschreibung für Ihren Kurs ein",
"create_button": "Erstellen",
"error": {
"failed_to_clone": "Der Kurs konnte nicht geklont werden"
}
}
},
"ai": {
@@ -540,6 +540,17 @@
"button": "Finish",
"back": "Back",
"next": "Next"
},
"copy_course": {
"title": "Create a Course",
"course_name_label": "Course name",
"course_name_placeholder": "Your course name",
"course_description_label": "Course description",
"course_description_placeholder": "Enter a description for your course",
"create_button": "Create",
"error": {
"failed_to_clone": "Failed to clone course"
}
}
},
@@ -937,6 +937,17 @@
"date_created": "fecha de creacion",
"published": "Publicado",
"lessons": "Lecciones"
},
"copy_course": {
"title": "Crear un curso",
"course_name_label": "Nombre del curso",
"course_name_placeholder": "Nombre de tu curso",
"course_description_label": "Descripción del curso",
"course_description_placeholder": "Introduce una descripción para tu curso.",
"create_button": "Crear",
"error": {
"failed_to_clone": "No se pudo clonar el curso"
}
}
},
"ai": {
@@ -937,6 +937,17 @@
"date_created": "date créée",
"published": "Publié",
"lessons": "Cours"
},
"copy_course": {
"title": "Créer un cours",
"course_name_label": "Nom du cours",
"course_name_placeholder": "Le nom de votre cours",
"course_description_label": "Descriptif du cours",
"course_description_placeholder": "Entrez une description pour votre cours",
"create_button": "Créer",
"error": {
"failed_to_clone": "Échec du clonage du cours"
}
}
},
"ai": {
@@ -937,6 +937,17 @@
"date_created": "बनाया गया दिनांक",
"published": "प्रकाशित",
"lessons": "पाठ"
},
"copy_course": {
"title": "एक कोर्स बनाएं",
"course_name_label": "कोर्स का नाम",
"course_name_placeholder": "आपके पाठ्यक्रम का नाम",
"course_description_label": "पाठ्यक्रम विवरण",
"course_description_placeholder": "अपने पाठ्यक्रम के लिए विवरण दर्ज करें",
"create_button": "बनाएं",
"error": {
"failed_to_clone": "पाठ्यक्रम क्लोन करने में विफल"
}
}
},
"ai": {
@@ -1086,6 +1086,17 @@
"date_created": "Utworzona data",
"published": "Opublikowany",
"lessons": "Lekcje"
},
"copy_course": {
"title": "Utwórz kurs",
"course_name_label": "Nazwa kursu",
"course_name_placeholder": "Nazwa Twojego kursu",
"course_description_label": "Opis kursu",
"course_description_placeholder": "Wprowadź opis swojego kursu",
"create_button": "Tworzyć",
"error": {
"failed_to_clone": "Nie udało się sklonować kursu"
}
}
},
"ai": {
@@ -937,6 +937,17 @@
"date_created": "Data Criada",
"published": "Publicados",
"lessons": "Lições"
},
"copy_course": {
"title": "Crie um curso",
"course_name_label": "Nome do curso",
"course_name_placeholder": "Nome do seu curso",
"course_description_label": "Descrição do curso",
"course_description_placeholder": "Insira uma descrição para o seu curso",
"create_button": "Criar",
"error": {
"failed_to_clone": "Falha ao clonar curso"
}
}
},
"ai": {
@@ -937,6 +937,17 @@
"date_created": "Дата создания",
"published": "Опубликовано",
"lessons": "Уроки"
},
"copy_course": {
"title": "Создать курс",
"course_name_label": "Название курса",
"course_name_placeholder": "Название вашего курса",
"course_description_label": "Описание курса",
"course_description_placeholder": "Введите описание вашего курса",
"create_button": "Создавать",
"error": {
"failed_to_clone": "Не удалось клонировать курс."
}
}
},
"ai": {
@@ -937,6 +937,17 @@
"date_created": "Ngày tạo",
"published": "Được phát hành",
"lessons": "Những bài học"
},
"copy_course": {
"title": "Tạo một khóa học",
"course_name_label": "Tên khóa học",
"course_name_placeholder": "Tên khóa học của bạn",
"course_description_label": "Mô tả khóa học",
"course_description_placeholder": "Nhập mô tả cho khóa học của bạn",
"create_button": "Tạo nên",
"error": {
"failed_to_clone": "Không thể sao chép khóa học"
}
}
},
"ai": {