[DONE] clone a course
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
import { get } from 'svelte/store';
|
||||
import type { PostgrestError } from '@supabase/supabase-js';
|
||||
import { supabase } from '../../functions/supabase';
|
||||
// @ts-ignore
|
||||
import { addGroupMember } from './index';
|
||||
// @ts-ignore
|
||||
import { profile } from '../../store/user';
|
||||
import type { Course, Exercise, Lesson, Profile } from '../../types';
|
||||
// @ts-ignore
|
||||
import { ROLE } from '../../constants/roles';
|
||||
// @ts-ignore
|
||||
import { QUESTION_TYPE } from '../../../components/Question/constants';
|
||||
|
||||
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 });
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
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_complete: lesson.is_complete,
|
||||
lesson_at: new Date(),
|
||||
note: lesson.note,
|
||||
order: lesson.order || index + 1,
|
||||
public: lesson.public,
|
||||
slide_url: lesson.slide_url,
|
||||
title: lesson.title,
|
||||
video_url: lesson.video_url,
|
||||
}));
|
||||
|
||||
const { data } = await supabase.from('lesson').insert(clonedLessons);
|
||||
|
||||
const newLessons = data?.map((dataItem, dataIndex) => {
|
||||
return {
|
||||
...dataItem,
|
||||
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) {
|
||||
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,
|
||||
});
|
||||
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,
|
||||
});
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
* 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);
|
||||
|
||||
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
|
||||
Overview
|
||||
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
|
||||
*/
|
||||
@@ -114,8 +114,8 @@ export async function updateCourse(courseId, course) {
|
||||
return course.logo;
|
||||
}
|
||||
|
||||
export async function deleteCourse(courseId, course) {
|
||||
return await supabase.from('course').delete(course).match({ id: courseId });
|
||||
export async function deleteCourse(courseId) {
|
||||
return await supabase.from('course').delete().match({ id: courseId });
|
||||
}
|
||||
|
||||
export function addGroupMember(member) {
|
||||
@@ -165,7 +165,7 @@ export function createExercise(exercise) {
|
||||
return supabase.from('exercise').insert(exercise);
|
||||
}
|
||||
|
||||
export function fetchExercises(courseId) {
|
||||
export function fetchExercisesByMarks(courseId) {
|
||||
return supabase.rpc('get_exercises').eq('course_id', courseId);
|
||||
}
|
||||
|
||||
|
||||
+205
-1
@@ -1,3 +1,207 @@
|
||||
export * from './config';
|
||||
export * from './dashboard';
|
||||
export * from './user';
|
||||
|
||||
export interface CurrentSessionType {
|
||||
access_token: string;
|
||||
expires_at: number;
|
||||
expires_in: number;
|
||||
refresh_token: string;
|
||||
token_type: string;
|
||||
}
|
||||
|
||||
export interface UserType {
|
||||
openAuthModal: boolean;
|
||||
fetchingUser: boolean;
|
||||
isLoggedIn: boolean;
|
||||
currentSession: CurrentSessionType | null;
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
export interface Organization {
|
||||
id: string /* primary key */;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Profile {
|
||||
id: string /* primary key */;
|
||||
fullname: string;
|
||||
username: string;
|
||||
avatar_url?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
email?: string;
|
||||
can_add_course?: boolean;
|
||||
}
|
||||
|
||||
export interface Question_type {
|
||||
id: number /* primary key */;
|
||||
label: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
typename?: string;
|
||||
}
|
||||
|
||||
export interface Submissionstatus {
|
||||
id: number /* primary key */;
|
||||
label: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface Group {
|
||||
id: string /* primary key */;
|
||||
name: string;
|
||||
description?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
organization_id?: string /* foreign key to organization.id */;
|
||||
organization?: Organization;
|
||||
}
|
||||
|
||||
export interface Role {
|
||||
type: string;
|
||||
description?: string;
|
||||
id: number /* primary key */;
|
||||
updated_at?: string;
|
||||
created_at?: string;
|
||||
}
|
||||
|
||||
export interface Waitinglist {
|
||||
id: number /* primary key */;
|
||||
email: string;
|
||||
created_at?: string;
|
||||
}
|
||||
|
||||
export interface Course {
|
||||
created_at?: string;
|
||||
description: string;
|
||||
group_id?: string /* foreign key to group.id */;
|
||||
group?: Group;
|
||||
id: string /* primary key */;
|
||||
is_template?: boolean;
|
||||
lessons: Lesson[];
|
||||
logo?: string;
|
||||
organization_id?: string /* foreign key to organization.id */;
|
||||
organization?: Organization;
|
||||
overview?: string;
|
||||
title: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface Groupmember {
|
||||
id: string /* primary key */;
|
||||
group_id: string /* foreign key to group.id */;
|
||||
role_id: number /* foreign key to role.id */;
|
||||
profile_id?: string /* foreign key to profile.id */;
|
||||
email?: string;
|
||||
created_at?: string;
|
||||
assigned_student_id?: string;
|
||||
group?: Group;
|
||||
role?: Role;
|
||||
profile?: Profile;
|
||||
}
|
||||
|
||||
export interface Organizationmember {
|
||||
id: number /* primary key */;
|
||||
organization_id: string /* foreign key to organization.id */;
|
||||
role_id: number /* foreign key to role.id */;
|
||||
profile_id: string /* foreign key to profile.id */;
|
||||
organization?: Organization;
|
||||
role?: Role;
|
||||
profile?: Profile;
|
||||
}
|
||||
|
||||
export interface Group_attendance {
|
||||
id: number /* primary key */;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
course_id?: string /* foreign key to course.id */;
|
||||
student_id?: string /* foreign key to groupmember.id */;
|
||||
is_present?: boolean;
|
||||
lesson_id: string;
|
||||
course?: Course;
|
||||
groupmember?: Groupmember;
|
||||
}
|
||||
|
||||
export interface Lesson {
|
||||
call_url?: string;
|
||||
course_id: string /* foreign key to course.id */;
|
||||
created_at: string;
|
||||
exercise?: Exercise[];
|
||||
id: string /* primary key */;
|
||||
is_complete?: boolean;
|
||||
lesson_at?: string;
|
||||
note?: string;
|
||||
order: number;
|
||||
profile?: Profile;
|
||||
public?: boolean;
|
||||
slide_url?: string;
|
||||
teacher_id?: string /* foreign key to profile.id */;
|
||||
title: string;
|
||||
updated_at?: string;
|
||||
video_url?: string;
|
||||
}
|
||||
|
||||
export interface Exercise {
|
||||
created_at?: string;
|
||||
description?: string;
|
||||
due_by?: string;
|
||||
id: string /* primary key */;
|
||||
lesson_id: string /* foreign key */;
|
||||
questions?: Question[];
|
||||
title: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface Question {
|
||||
created_at?: string;
|
||||
exercise_id: string /* foreign key to exercise.id */;
|
||||
id: number /* primary key */;
|
||||
name?: string;
|
||||
options: Option[];
|
||||
order?: number;
|
||||
points?: string;
|
||||
question_type_id: number /* foreign key to question_type.id */;
|
||||
title: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface Submission {
|
||||
id: string /* primary key */;
|
||||
reviewer_id?: number;
|
||||
status_id?: number /* foreign key to submissionstatus.id */;
|
||||
total?: number;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
exercise_id: string /* foreign key to exercise.id */;
|
||||
submitted_by?: string /* foreign key to groupmember.id */;
|
||||
course_id?: string /* foreign key to course.id */;
|
||||
submissionstatus?: Submissionstatus;
|
||||
exercise?: Exercise;
|
||||
groupmember?: Groupmember;
|
||||
course?: Course;
|
||||
}
|
||||
|
||||
export interface Option {
|
||||
created_at?: string;
|
||||
id?: number /* primary key */;
|
||||
is_correct: boolean;
|
||||
is_dirty?: boolean;
|
||||
label: string;
|
||||
question_id: number /* foreign key to question.id */;
|
||||
question?: Question;
|
||||
updated_at?: string;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
export interface Question_answer {
|
||||
id: number /* primary key */;
|
||||
answers?: any[];
|
||||
question_id: number /* foreign key to question.id */;
|
||||
open_answer?: string;
|
||||
group_member_id: string /* foreign key to groupmember.id */;
|
||||
submission_id?: string /* foreign key to submission.id */;
|
||||
point?: number;
|
||||
question?: Question;
|
||||
groupmember?: Groupmember;
|
||||
submission?: Submission;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user