[DONE] clone a course

This commit is contained in:
Rotimi Best
2021-12-30 22:30:30 +02:00
parent a1f6c5728b
commit 23c68c9d78
24 changed files with 834 additions and 195 deletions
+248
View File
@@ -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
*/
+3 -3
View File
@@ -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
View File
@@ -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;
}