Skip to content

Commit

Permalink
feat: new lesson view
Browse files Browse the repository at this point in the history
  • Loading branch information
piotr-pajak committed Dec 27, 2024
1 parent f6f0f89 commit 515419d
Show file tree
Hide file tree
Showing 30 changed files with 1,530 additions and 699 deletions.
6 changes: 3 additions & 3 deletions apps/api/src/chapter/chapter.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import { ChapterService } from "./chapter.service";
import {
CreateChapterBody,
createChapterSchema,
showChapterSchema,
UpdateChapterBody,
updateChapterSchema,
showChapterSchema,
} from "./schemas/chapter.schema";

import type { ShowChapterResponse } from "./schemas/chapter.schema";
import type { ChapterResponse } from "./schemas/chapter.schema";

@Controller("chapter")
@UseGuards(RolesGuard)
Expand All @@ -38,7 +38,7 @@ export class ChapterController {
@Query("id") id: UUIDType,
@CurrentUser("role") userRole: UserRole,
@CurrentUser("userId") userId: UUIDType,
): Promise<BaseResponse<ShowChapterResponse>> {
): Promise<BaseResponse<ChapterResponse>> {
return new BaseResponse(
await this.chapterService.getChapterWithLessons(id, userId, userRole === USER_ROLES.ADMIN),
);
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/chapter/chapter.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { LessonRepository } from "src/lesson/repositories/lesson.repository";

import { ChapterRepository } from "./repositories/chapter.repository";

import type { ShowChapterResponse } from "src/chapter/schemas/chapter.schema";
import type { ChapterResponse } from "src/chapter/schemas/chapter.schema";
import type { UUIDType } from "src/common";

@Injectable()
Expand All @@ -22,7 +22,7 @@ export class ChapterService {
id: UUIDType,
userId: UUIDType,
isAdmin?: boolean,
): Promise<ShowChapterResponse> {
): Promise<ChapterResponse> {
const [courseAccess] = await this.chapterRepository.checkChapterAssignment(id, userId);
const chapter = await this.chapterRepository.getChapterForUser(id, userId);

Expand Down
13 changes: 3 additions & 10 deletions apps/api/src/chapter/schemas/chapter.schema.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { type Static, Type } from "@sinclair/typebox";

import { UUIDSchema } from "src/common";
import { lessonSchema } from "src/lesson/lesson.schema";
import { lessonForChapterSchema, lessonSchema } from "src/lesson/lesson.schema";
import { PROGRESS_STATUSES } from "src/utils/types/progress.type";

export const chapterSchema = Type.Object({
id: UUIDSchema,
title: Type.String(),
lessonCount: Type.Number(),
lessons: Type.Optional(Type.Array(lessonSchema)),
completedLessonCount: Type.Optional(Type.Number()),
chapterProgress: Type.Optional(
Type.Union([
Expand Down Expand Up @@ -40,23 +41,15 @@ export const chapter = Type.Object({
isFreemium: Type.Boolean(),
});

export const chapterWithLessonCount = Type.Intersect([
Type.Omit(chapter, ["type"]),
Type.Object({
lessonCount: Type.Number(),
}),
]);

export const allChapterSchema = Type.Array(chapterSchema);

export const showChapterSchema = Type.Object({
...chapterSchema.properties,
quizCount: Type.Optional(Type.Number()),
lessons: Type.Array(lessonSchema),
lessons: lessonForChapterSchema,
});

export type Chapter = Static<typeof chapter>;
export type ChapterWithLessonCount = Static<typeof chapterWithLessonCount>;
export type ChapterResponse = Static<typeof chapterSchema>;
export type ShowChapterResponse = Static<typeof showChapterSchema>;
export type AllChaptersResponse = Static<typeof allChapterSchema>;
Expand Down
24 changes: 10 additions & 14 deletions apps/api/src/courses/course.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ import { CourseService } from "src/courses/course.service";
import { allCoursesSchema } from "src/courses/schemas/course.schema";
import { SortCourseFieldsOptions } from "src/courses/schemas/courseQuery";
import { CreateCourseBody, createCourseSchema } from "src/courses/schemas/createCourse.schema";
import { commonShowCourseSchema } from "src/courses/schemas/showCourseCommon.schema";
import {
commonShowBetaCourseSchema,
commonShowCourseSchema,
} from "src/courses/schemas/showCourseCommon.schema";
import { UpdateCourseBody, updateCourseSchema } from "src/courses/schemas/updateCourse.schema";
import {
allCoursesValidation,
Expand All @@ -45,7 +48,10 @@ import type {
AllCoursesResponse,
} from "src/courses/schemas/course.schema";
import type { CoursesFilterSchema } from "src/courses/schemas/courseQuery";
import type { CommonShowCourse } from "src/courses/schemas/showCourseCommon.schema";
import type {
CommonShowBetaCourse,
CommonShowCourse,
} from "src/courses/schemas/showCourseCommon.schema";

@Controller("course")
@UseGuards(RolesGuard)
Expand Down Expand Up @@ -193,23 +199,13 @@ export class CourseController {
return new BaseResponse(await this.courseService.getCourse(id, currentUserId));
}

@Get("course-by-id")
@Roles(USER_ROLES.TEACHER, USER_ROLES.ADMIN)
@Validate({
request: [{ type: "query", name: "id", schema: UUIDSchema, required: true }],
response: baseResponse(commonShowCourseSchema),
})
async getCourseById(@Query("id") id: UUIDType): Promise<BaseResponse<CommonShowCourse>> {
return new BaseResponse(await this.courseService.getCourseById(id));
}

@Get("beta-course-by-id")
@Roles(USER_ROLES.TEACHER, USER_ROLES.ADMIN)
@Validate({
request: [{ type: "query", name: "id", schema: UUIDSchema, required: true }],
response: baseResponse(commonShowCourseSchema),
response: baseResponse(commonShowBetaCourseSchema),
})
async getBetaCourseById(@Query("id") id: UUIDType): Promise<BaseResponse<CommonShowCourse>> {
async getBetaCourseById(@Query("id") id: UUIDType): Promise<BaseResponse<CommonShowBetaCourse>> {
return new BaseResponse(await this.courseService.getBetaCourseById(id));
}

Expand Down
23 changes: 13 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 { AdminLessonWithContentSchema } 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,7 @@ export class CourseService {
`,
isFreemium: chapters.isFreemium,
displayOrder: sql<number>`${chapters.displayOrder}`,
lessons: sql<string>`
lessons: sql<LessonForChapterSchema>`
COALESCE(
(
SELECT json_agg(lesson_data)
Expand Down Expand Up @@ -466,15 +469,15 @@ export class CourseService {
displayOrder: sql<number>`${chapters.displayOrder}`,
lessonCount: chapters.lessonCount,
isFree: chapters.isFreemium,
lessons: sql<string>`
lessons: sql<LessonForChapterSchema>`
COALESCE(
(
SELECT array_agg(${lessons.id} ORDER BY ${lessons.displayOrder})
FROM ${lessons}
WHERE ${lessons.chapterId} = ${chapters.id}
),
'{}'
)
(
SELECT array_agg(${lessons.id} ORDER BY ${lessons.displayOrder})
FROM ${lessons}
WHERE ${lessons.chapterId} = ${chapters.id}
),
'{}'
)
`,
})
.from(chapters)
Expand Down
23 changes: 22 additions & 1 deletion apps/api/src/courses/schemas/showCourseCommon.schema.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
import { type Static, Type } from "@sinclair/typebox";

import { chapterSchema } from "src/chapter/schemas/chapter.schema";
import { chapterSchema, showChapterSchema } from "src/chapter/schemas/chapter.schema";
import { UUIDSchema } from "src/common";

export const commonShowCourseSchema = Type.Object({
archived: Type.Optional(Type.Boolean()),
authorId: Type.Optional(UUIDSchema),
category: Type.String(),
categoryId: Type.Optional(Type.String({ format: "uuid" })),
chapters: Type.Array(showChapterSchema),
completedChapterCount: Type.Optional(Type.Number()),
courseChapterCount: Type.Number(),
currency: Type.String(),
description: Type.String(),
enrolled: Type.Optional(Type.Boolean()),
hasFreeChapter: Type.Optional(Type.Boolean()),
id: Type.String({ format: "uuid" }),
isPublished: Type.Union([Type.Boolean(), Type.Null()]),
isScorm: Type.Optional(Type.Boolean()),
priceInCents: Type.Number(),
thumbnailUrl: Type.Optional(Type.String()),
title: Type.String(),
});

export const commonShowBetaCourseSchema = Type.Object({
archived: Type.Optional(Type.Boolean()),
authorId: Type.Optional(UUIDSchema),
category: Type.String(),
Expand All @@ -26,3 +46,4 @@ export const commonShowCourseSchema = Type.Object({
});

export type CommonShowCourse = Static<typeof commonShowCourseSchema>;
export type CommonShowBetaCourse = Static<typeof commonShowBetaCourseSchema>;
32 changes: 27 additions & 5 deletions apps/api/src/lesson/lesson.schema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Type } from "@sinclair/typebox";

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

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

Expand All @@ -18,11 +19,11 @@ export const optionSchema = Type.Object({
export const questionSchema = Type.Object({
id: Type.Optional(UUIDSchema),
type: Type.Enum(QuestionType),
description: Type.Optional(Type.String()),
description: Type.Optional(Type.Union([Type.String(), Type.Null()])),
title: Type.String(),
displayOrder: Type.Optional(Type.Number()),
photoQuestionType: Type.Optional(Type.Enum(PhotoQuestionType)),
photoS3Key: Type.Optional(Type.String()),
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)),
});

Expand All @@ -32,8 +33,8 @@ export const lessonSchema = Type.Object({
type: Type.String(),
description: Type.String(),
displayOrder: Type.Number(),
fileS3Key: Type.Optional(Type.String()),
fileType: Type.Optional(Type.String()),
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()),
});
Expand Down Expand Up @@ -97,8 +98,28 @@ export const lessonShowSchema = Type.Object({

export const updateLessonSchema = Type.Partial(createLessonSchema);
export const updateQuizLessonSchema = Type.Partial(createQuizLessonSchema);
export const lessonForChapterSchema = Type.Array(
Type.Object({
id: UUIDSchema,
title: 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.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()]),
}),
);

export type AdminLessonWithContentSchema = Static<typeof adminLessonSchema>;
export type LessonForChapterSchema = Static<typeof lessonForChapterSchema>;
export type CreateLessonBody = Static<typeof createLessonSchema>;
export type UpdateLessonBody = Static<typeof updateLessonSchema>;
export type UpdateQuizLessonBody = Static<typeof updateQuizLessonSchema>;
Expand All @@ -108,3 +129,4 @@ export type OptionBody = Static<typeof optionSchema>;
export type QuestionBody = Static<typeof questionSchema>;
export type QuestionSchema = Static<typeof questionSchema>;
export type LessonShow = Static<typeof lessonShowSchema>;
export type LessonSchema = Static<typeof lessonSchema>;
33 changes: 33 additions & 0 deletions apps/api/src/lesson/repositories/lesson.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,39 @@ export class LessonRepository {
.orderBy(lessons.displayOrder);
}

async getLessonsForChapter(chapterId: UUIDType) {
return this.db
.select({
title: lessons.title,
type: lessons.type,
displayOrder: sql<number>`${lessons.displayOrder}`,
questions: sql<QuestionBody[]>`
COALESCE(
(
SELECT json_agg(questions_data)
FROM (
SELECT
${questions.id} AS id,
${questions.title} AS title,
${questions.description} AS description,
${questions.type} AS type,
${questions.photoQuestionType} AS photoQuestionType,
${questions.photoS3Key} AS photoS3Key,
${questions.solutionExplanation} AS solutionExplanation,
-- TODO: add display order
FROM ${questions}
WHERE ${lessons.id} = ${questions.lessonId}
) AS questions_data
),
'[]'::json
)
`,
})
.from(lessons)
.where(eq(lessons.chapterId, chapterId))
.orderBy(lessons.displayOrder);
}

// async completeQuiz(
// courseId: UUIDType,
// lessonId: UUIDType,
Expand Down
6 changes: 1 addition & 5 deletions apps/api/src/lesson/services/lesson.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@ import { eq, sql } from "drizzle-orm";

import { DatabasePg } from "src/common";
import { FileService } from "src/file/file.service";
import {
lessons,
questionAnswerOptions,
questions,
} from "src/storage/schema";
import { lessons, questionAnswerOptions, questions } from "src/storage/schema";

import { LESSON_TYPES } from "../lesson.type";

Expand Down
5 changes: 5 additions & 0 deletions apps/api/src/seed/e2e-data-seeds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const e2eCourses: NiceCourseData[] = [
displayOrder: 1,
lessons: [
{
id: crypto.randomUUID(),
type: LESSON_TYPES.TEXT,
title: "E2E Testing Text Block",
description: "E2E Testing Text Block Body",
Expand All @@ -32,12 +33,14 @@ export const e2eCourses: NiceCourseData[] = [
displayOrder: 2,
lessons: [
{
id: crypto.randomUUID(),
type: LESSON_TYPES.QUIZ,
title: "E2E Testing Quiz",
description: "E2E Testing Quiz Description",
displayOrder: 1,
questions: [
{
id: crypto.randomUUID(),
type: QuestionType.SingleChoice,
title: "E2E Testing Question",
description: "E2E Testing Question",
Expand All @@ -50,6 +53,7 @@ export const e2eCourses: NiceCourseData[] = [
],
},
{
id: crypto.randomUUID(),
type: QuestionType.SingleChoice,
title: "E2E Testing Question 2",
description: "E2E Testing Question 2",
Expand All @@ -67,6 +71,7 @@ export const e2eCourses: NiceCourseData[] = [
],
},
{
id: crypto.randomUUID(),
type: QuestionType.MultipleChoice,
title: "E2E Testing Question 3",
description: "E2E Testing Question 3",
Expand Down
Loading

0 comments on commit 515419d

Please sign in to comment.