Skip to content

Commit

Permalink
Merge branch 'main' into jw_feat_lc-501_lesson_presentation
Browse files Browse the repository at this point in the history
  • Loading branch information
typeWolffo committed Dec 27, 2024
2 parents 7cecb21 + c63f019 commit 5697e4a
Show file tree
Hide file tree
Showing 70 changed files with 7,994 additions and 1,402 deletions.
4 changes: 2 additions & 2 deletions apps/api/src/chapter/adminChapter.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class AdminChapterService {
// },
// };
// }
// async processLessonItems(lessonItemsList: LessonItemWithContentSchema[]) {
// async processLessonItems(lessonItemsList: AdminLessonWithContentSchema[]) {
// const getFileUrl = async (url: string) => {
// if (!url || url.startsWith("https://")) return url;
// return await this.s3Service.getSignedUrl(url);
Expand Down Expand Up @@ -123,7 +123,7 @@ export class AdminChapterService {

// const lessonItemsList = await this.adminChapterRepository.getLessonItems(id);

// const items = await this.processLessonItems(lessonItemsList as LessonItemWithContentSchema[]);
// const items = await this.processLessonItems(lessonItemsList as AdminLessonWithContentSchema[]);

// return {
// ...lesson,
Expand Down
7 changes: 4 additions & 3 deletions apps/api/src/chapter/repositories/adminChapter.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { chapters, lessons, questionAnswerOptions, questions } from "src/storage

import type { UpdateChapterBody } from "../schemas/chapter.schema";
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
import type { LessonItemWithContentSchema, QuestionSchema } from "src/lesson/lesson.schema";
import type { AdminLessonWithContentSchema, QuestionSchema } from "src/lesson/lesson.schema";
import type * as schema from "src/storage/schema";

@Injectable()
Expand Down Expand Up @@ -209,7 +209,7 @@ export class AdminChapterRepository {
// // : await this.fileService.getFileUrl(lesson.imageUrl),
// // };

async getBetaChapterLessons(chapterId: UUIDType): Promise<LessonItemWithContentSchema[]> {
async getBetaChapterLessons(chapterId: UUIDType): Promise<AdminLessonWithContentSchema[]> {
return await this.db
.select({
updatedAt: sql<string>`${lessons.updatedAt}`,
Expand All @@ -229,14 +229,15 @@ export class AdminChapterRepository {
'type', ${questions.type},
'description', ${questions.description},
'photoS3Key', ${questions.photoS3Key},
'displayOrder', ${questions.displayOrder},
'photoQuestionType', ${questions.photoQuestionType},
'options', (
SELECT ARRAY(
SELECT json_build_object(
'id', ${questionAnswerOptions.id},
'optionText', ${questionAnswerOptions.optionText},
'isCorrect', ${questionAnswerOptions.isCorrect},
'position', ${questionAnswerOptions.position}
'displayOrder', ${questionAnswerOptions.displayOrder}
)
FROM ${questionAnswerOptions} questionAnswerOptions
WHERE questionAnswerOptions.question_id = questions.id
Expand Down
24 changes: 14 additions & 10 deletions apps/api/src/courses/course.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ import type { CommonShowCourse } from "./schemas/showCourseCommon.schema";
import type { UpdateCourseBody } from "./schemas/updateCourse.schema";
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
import type { Pagination, UUIDType } from "src/common";
import type { LessonForChapterSchema, LessonItemWithContentSchema } from "src/lesson/lesson.schema";
import type {
AdminLessonWithContentSchema,
LessonForChapterSchema,
} from "src/lesson/lesson.schema";
import type * as schema from "src/storage/schema";
import type { ProgressStatus } from "src/utils/types/progress.type";

Expand Down Expand Up @@ -378,7 +381,8 @@ export class CourseService {
`,
isFreemium: chapters.isFreemium,
displayOrder: sql<number>`${chapters.displayOrder}`,
lessons: sql<LessonForChapterSchema>`COALESCE(
lessons: sql<LessonForChapterSchema>`
COALESCE(
(
SELECT json_agg(lesson_data)
FROM (
Expand Down Expand Up @@ -443,7 +447,7 @@ export class CourseService {
.select({
id: courses.id,
title: courses.title,
thumbnailUrl: sql<string>`COALESCE(${courses.thumbnailS3Key}, '')`,
thumbnailS3Key: sql<string>`COALESCE(${courses.thumbnailS3Key}, '')`,
category: categories.title,
categoryId: categories.id,
description: sql<string>`${courses.description}`,
Expand Down Expand Up @@ -485,11 +489,11 @@ export class CourseService {
return await this.fileService.getFileUrl(url);
};

const thumbnailUrl = await getImageUrl(course.thumbnailUrl);
const thumbnailS3SingedUrl = await getImageUrl(course.thumbnailS3Key);

const updatedCourseLessonList = await Promise.all(
courseChapterList?.map(async (chapter) => {
const lessons: LessonItemWithContentSchema[] =
const lessons: AdminLessonWithContentSchema[] =
await this.adminChapterRepository.getBetaChapterLessons(chapter.id);

const lessonsWithSignedUrls = await this.addS3SignedUrlsToLessonsAndQuestions(lessons);
Expand All @@ -503,7 +507,7 @@ export class CourseService {

return {
...course,
thumbnailUrl,
thumbnailS3SingedUrl,
chapters: updatedCourseLessonList ?? [],
};
}
Expand All @@ -513,7 +517,7 @@ export class CourseService {
.select({
id: courses.id,
title: courses.title,
thumbnailUrl: sql<string>`${courses.thumbnailS3Key}`,
thumbnailS3Key: sql<string>`${courses.thumbnailS3Key}`,
category: categories.title,
categoryId: categories.id,
description: sql<string>`${courses.description}`,
Expand Down Expand Up @@ -552,11 +556,11 @@ export class CourseService {
return await this.fileService.getFileUrl(url);
};

const thumbnailUrl = await getImageUrl(course.thumbnailUrl);
const thumbnailS3SingedUrl = await getImageUrl(course.thumbnailS3Key);

return {
...course,
thumbnailUrl,
thumbnailS3SingedUrl,
chapters: courseChapterList,
};
}
Expand Down Expand Up @@ -927,7 +931,7 @@ export class CourseService {
);
}

private async addS3SignedUrlsToLessonsAndQuestions(lessons: LessonItemWithContentSchema[]) {
private async addS3SignedUrlsToLessonsAndQuestions(lessons: AdminLessonWithContentSchema[]) {
return await Promise.all(
lessons.map(async (lesson) => {
const updatedLesson = { ...lesson };
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/courses/schemas/showCourseCommon.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ export const commonShowBetaCourseSchema = Type.Object({
isScorm: Type.Optional(Type.Boolean()),
priceInCents: Type.Number(),
thumbnailUrl: Type.Optional(Type.String()),
thumbnailS3Key: Type.Optional(Type.String()),
thumbnailS3SingedUrl: Type.Optional(Type.String()),
title: Type.String(),
});

Expand Down
70 changes: 18 additions & 52 deletions apps/api/src/lesson/lesson.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,22 @@ import { CurrentUser } from "src/common/decorators/user.decorator";
import { RolesGuard } from "src/common/guards/roles.guard";
import { USER_ROLES } from "src/user/schemas/userRoles";

import { AdminLessonService } from "./services/adminLesson.service";
import {
CreateLessonBody,
createLessonSchema,
CreateQuizLessonBody,
createQuizLessonSchema,
LessonShow,
lessonShowSchema,
UpdateLessonBody,
updateLessonSchema,
UpdateQuizLessonBody,
updateQuizLessonSchema,
} from "./lesson.schema";
import { AdminLessonService } from "./services/adminLesson.service";
import { LessonService } from "./services/lesson.service";

import type { LessonShow } from "./lesson.schema";

@Controller("lesson")
@UseGuards(RolesGuard)
export class LessonController {
Expand Down Expand Up @@ -157,56 +158,21 @@ export class LessonController {
});
}

// @Patch("course-lesson")
// @Roles(USER_ROLES.TEACHER, USER_ROLES.ADMIN)
// @Validate({
// request: [
// {
// type: "body",
// schema: Type.Object({
// courseId: UUIDSchema,
// lessonId: UUIDSchema,
// isFree: Type.Boolean(),
// }),
// },
// ],
// response: baseResponse(Type.Object({ isFree: Type.Boolean(), message: Type.String() })),
// })
// async toggleLessonAsFree(
// @Body() body: { courseId: string; lessonId: string; isFree: boolean },
// ): Promise<BaseResponse<{ isFree: boolean; message: string }>> {
// const [toggledLesson] = await this.adminLessonsService.toggleLessonAsFree(
// body.courseId,
// body.lessonId,
// body.isFree,
// );
// return new BaseResponse({
// isFree: toggledLesson.isFree,
// message: body.isFree
// ? "Lesson toggled as free successfully"
// : "Lesson toggled as not free successfully",
// });
// }

// @Post("evaluation-quiz")
// @Roles(USER_ROLES.STUDENT)
// @Validate({
// request: [
// { type: "query", name: "courseId", schema: UUIDSchema },
// { type: "query", name: "lessonId", schema: UUIDSchema },
// ],
// response: baseResponse(Type.Object({ message: Type.String() })),
// })
// async evaluationQuiz(
// @Query("courseId") courseId: string,
// @Query("lessonId") lessonId: string,
// @CurrentUser("userId") currentUserId: UUIDType,
// ): Promise<BaseResponse<{ message: string }>> {
// await this.lessonsService.evaluationQuiz(courseId, lessonId, currentUserId);
// return new BaseResponse({
// message: "Evaluation quiz successfully",
// });
// }
// @Post("evaluation-quiz")
// @Roles(USER_ROLES.STUDENT)
// @Validate({
// request: [{ type: "query", name: "lessonId", schema: UUIDSchema, required: true }],
// response: baseResponse(Type.Object({ message: Type.String() })),
// })
// async evaluationQuiz(
// @Query("lessonId") lessonId: string,
// @CurrentUser("userId") currentUserId: UUIDType,
// ): Promise<BaseResponse<{ message: string }>> {
// await this.lessonService.evaluationQuiz(lessonId, currentUserId);
// return new BaseResponse({
// message: "Evaluation quiz successfully",
// });
// }

// @Delete("clear-quiz-progress")
// @Roles(USER_ROLES.STUDENT)
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/lesson/lesson.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { Module } from "@nestjs/common";

import { FileModule } from "src/file/files.module";

import { AdminLessonRepository } from "./repositories/adminLesson.repository";
import { AdminLessonService } from "./services/adminLesson.service";
import { LessonController } from "./lesson.controller";
import { AdminLessonRepository } from "./repositories/adminLesson.repository";
import { LessonRepository } from "./repositories/lesson.repository";
import { AdminLessonService } from "./services/adminLesson.service";
import { LessonService } from "./services/lesson.service";

@Module({
Expand Down
48 changes: 32 additions & 16 deletions apps/api/src/lesson/lesson.schema.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
import { Type } from "@sinclair/typebox";

import { UUIDSchema } from "src/common";
import { PROGRESS_STATUSES } from "src/utils/types/progress.type";

import { PhotoQuestionType, QuestionType } from "./lesson.type";
import { LESSON_TYPES, PhotoQuestionType, QuestionType } from "./lesson.type";

import type { Static } from "@sinclair/typebox";

export const optionSchema = Type.Object({
id: Type.Optional(UUIDSchema),
optionText: Type.String(),
displayOrder: Type.Union([Type.Number(), Type.Null()]),
isStudentAnswer: Type.Optional(Type.Boolean()),
isCorrect: Type.Boolean(),
position: Type.Number(),
questionId: Type.Optional(UUIDSchema),
});

export const questionSchema = Type.Object({
id: Type.Optional(UUIDSchema),
type: Type.Enum(QuestionType),
description: Type.Optional(Type.Union([Type.String(), Type.Null()])),
title: Type.String(),
displayOrder: Type.Optional(Type.Number()),
photoQuestionType: Type.Optional(Type.Union([Type.Enum(PhotoQuestionType), Type.Null()])),
photoS3Key: Type.Optional(Type.Union([Type.String(), Type.Null()])),
options: Type.Optional(Type.Array(optionSchema)),
});

export const lessonSchema = Type.Object({
updatedAt: Type.Optional(Type.String()),
id: UUIDSchema,
title: Type.String(),
type: Type.String(),
Expand All @@ -33,6 +36,7 @@ export const lessonSchema = Type.Object({
fileS3Key: Type.Optional(Type.Union([Type.String(), Type.Null()])),
fileType: Type.Optional(Type.Union([Type.String(), Type.Null()])),
questions: Type.Optional(Type.Array(questionSchema)),
updatedAt: Type.Optional(Type.String()),
});

const lessonQuizSchema = Type.Object({
Expand All @@ -46,7 +50,7 @@ const lessonQuizSchema = Type.Object({
questions: Type.Optional(Type.Array(questionSchema)),
});

export const lessonItemSchema = Type.Object({
export const adminLessonSchema = Type.Object({
id: UUIDSchema,
type: Type.String(),
displayOrder: Type.Number(),
Expand All @@ -73,14 +77,23 @@ export const createQuizLessonSchema = Type.Intersect([
}),
]);

export const questionDetails = Type.Object({
questions: Type.Array(Type.Any()),
questionCount: Type.Number(),
correctAnswerCount: Type.Union([Type.Number(), Type.Null()]),
wrongAnswerCount: Type.Union([Type.Number(), Type.Null()]),
score: Type.Union([Type.Number(), Type.Null()]),
});

export const lessonShowSchema = Type.Object({
id: UUIDSchema,
title: Type.String(),
type: Type.String(),
type: Type.Enum(LESSON_TYPES),
description: Type.String(),
fileType: Type.Union([Type.String(), Type.Null()]),
fileUrl: Type.Union([Type.String(), Type.Null()]),
questions: Type.Optional(Type.Array(Type.Any())),
quizDetails: Type.Optional(questionDetails),
displayOrder: Type.Number(),
});

export const updateLessonSchema = Type.Partial(createLessonSchema);
Expand All @@ -89,27 +102,30 @@ export const lessonForChapterSchema = Type.Array(
Type.Object({
id: UUIDSchema,
title: Type.String(),
type: Type.String(),
type: Type.Union([
Type.Literal(LESSON_TYPES.QUIZ),
Type.Literal(LESSON_TYPES.PRESENTATION),
Type.Literal(LESSON_TYPES.VIDEO),
Type.Literal(LESSON_TYPES.TEXT),
]),
displayOrder: Type.Number(),
status: Type.String(),
status: Type.Union([
Type.Literal(PROGRESS_STATUSES.COMPLETED),
Type.Literal(PROGRESS_STATUSES.IN_PROGRESS),
Type.Literal(PROGRESS_STATUSES.NOT_STARTED),
]),
quizQuestionCount: Type.Union([Type.Number(), Type.Null()]),
}),
// Type.Intersect([
// Type.Omit(lessonSchema, ["updatedAt", "description", "fileS3Key", "fileType", "questions"]),
// Type.Object({
// status: Type.String(),
// quizQuestionCount: Type.Union([Type.Number(), Type.Null()]),
// }),
// ]),
);

export type AdminLessonWithContentSchema = Static<typeof adminLessonSchema>;
export type LessonForChapterSchema = Static<typeof lessonForChapterSchema>;
export type LessonItemWithContentSchema = Static<typeof lessonItemSchema>;
export type CreateLessonBody = Static<typeof createLessonSchema>;
export type UpdateLessonBody = Static<typeof updateLessonSchema>;
export type UpdateQuizLessonBody = Static<typeof updateQuizLessonSchema>;
export type CreateQuizLessonBody = Static<typeof createQuizLessonSchema>;
// TODO: duplicate
export type OptionBody = Static<typeof optionSchema>;
export type QuestionBody = Static<typeof questionSchema>;
export type QuestionSchema = Static<typeof questionSchema>;
export type LessonShow = Static<typeof lessonShowSchema>;
Expand Down
5 changes: 3 additions & 2 deletions apps/api/src/lesson/lesson.type.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export const LESSON_TYPES = {
TEXT_BLOCK: "text_block",
TEXT: "text",
FILE: "file",
PRESENTATION: "presentation",
VIDEO: "video",
Expand All @@ -11,7 +11,8 @@ export enum QuestionType {
MultipleChoice = "multiple_choice",
TrueOrFalse = "true_or_false",
PhotoQuestion = "photo_question",
FillInTheBlanks = "fill_in_the_blanks",
FillInTheBlanksText = "fill_in_the_blanks_text",
FillInTheBlanksDnd = "fill_in_the_blanks_dnd",
BriefResponse = "brief_response",
DetailedResponse = "detailed_response",
}
Expand Down
Loading

0 comments on commit 5697e4a

Please sign in to comment.