Skip to content

Commit

Permalink
feat: store lesson progress in db
Browse files Browse the repository at this point in the history
  • Loading branch information
wielopolski committed Nov 21, 2024
1 parent df3c6ea commit 781c58e
Show file tree
Hide file tree
Showing 26 changed files with 516 additions and 411 deletions.
5 changes: 3 additions & 2 deletions apps/api/src/lessons/lessons.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ export class LessonsService {
}

private async getLessonItems(lesson: Lesson, courseId: UUIDType, userId: UUIDType) {
const lessonItemsList = await this.lessonsRepository.getLessonItems(lesson.id);
const lessonItemsList = await this.lessonsRepository.getLessonItems(lesson.id, courseId);
const validLessonItemsList = lessonItemsList.filter(this.isValidItem);

return await Promise.all(
Expand Down Expand Up @@ -337,7 +337,7 @@ export class LessonsService {
userId: UUIDType,
quizCompleted: boolean,
) {
const lessonItemsList = await this.lessonsRepository.getLessonItems(lesson.id);
const lessonItemsList = await this.lessonsRepository.getLessonItems(lesson.id, courseId);
const validLessonItemsList = lessonItemsList.filter(this.isValidItem);

return await Promise.all(
Expand Down Expand Up @@ -409,6 +409,7 @@ export class LessonsService {
lessonItemId: item.lessonItemId,
lessonItemType: item.lessonItemType,
displayOrder: item.displayOrder,
isCompleted: item.isCompleted,
content,
};
}
Expand Down
37 changes: 36 additions & 1 deletion apps/api/src/lessons/repositories/lessons.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,32 @@ export class LessonsRepository {
return lesson;
}

async getLesson(courseId: UUIDType, lessonId: UUIDType) {
const [lesson] = await this.db
.select({
id: lessons.id,
title: lessons.title,
description: sql<string>`${lessons.description}`,
imageUrl: sql<string>`${lessons.imageUrl}`,
type: sql<string>`${lessons.type}`,
isFree: courseLessons.isFree,
})
.from(lessons)
.innerJoin(
courseLessons,
and(eq(courseLessons.lessonId, lessons.id), eq(courseLessons.courseId, courseId)),
)
.where(
and(
eq(lessons.id, lessonId),
eq(lessons.archived, false),
eq(lessons.state, STATES.published),
),
);

return lesson;
}

async getQuestionItems(
lessonId: UUIDType,
studentId: UUIDType,
Expand Down Expand Up @@ -113,7 +139,7 @@ export class LessonsRepository {
.orderBy(lessonItems.displayOrder);
}

async getLessonItems(lessonId: UUIDType) {
async getLessonItems(lessonId: UUIDType, courseId: UUIDType) {
return await this.db
.select({
lessonItemType: lessonItems.lessonItemType,
Expand All @@ -122,6 +148,7 @@ export class LessonsRepository {
textBlockData: textBlocks,
fileData: files,
displayOrder: lessonItems.displayOrder,
isCompleted: sql<boolean>`CASE WHEN ${studentCompletedLessonItems.id} IS NOT NULL THEN true ELSE false END`,
})
.from(lessonItems)
.leftJoin(
Expand All @@ -148,6 +175,14 @@ export class LessonsRepository {
eq(files.state, STATES.published),
),
)
.leftJoin(
studentCompletedLessonItems,
and(
eq(studentCompletedLessonItems.lessonItemId, lessonItems.id),
eq(studentCompletedLessonItems.lessonId, lessonId),
eq(studentCompletedLessonItems.courseId, courseId),
),
)
.where(and(eq(lessonItems.lessonId, lessonId)))
.orderBy(lessonItems.displayOrder);
}
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/lessons/schemas/lessonItem.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export const lessonItemSchema = Type.Object({

export const lessonItemWithContent = Type.Object({
...lessonItemSchema.properties,
isCompleted: Type.Boolean(),
questionData: Type.Union([questionSchema, Type.Null()]),
textBlockData: Type.Union([textBlockSchema, Type.Null()]),
fileData: Type.Union([lessonItemFileSchema, Type.Null()]),
Expand Down Expand Up @@ -142,6 +143,7 @@ export const lessonItemSelectSchema = Type.Object({
lessonItemType: Type.String(),
displayOrder: Type.Union([Type.Number(), Type.Null()]),
passQuestion: Type.Optional(Type.Union([Type.Null(), Type.Unknown()])),
isCompleted: Type.Optional(Type.Boolean()),
content: Type.Union([questionContentResponse, textBlockContentResponse, fileContentResponse]),
});

Expand Down
8 changes: 7 additions & 1 deletion apps/api/src/questions/questions.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ import { LessonsRepository } from "src/lessons/repositories/lessons.repository";
import { StudentCompletedLessonItemsService } from "src/studentCompletedLessonItem/studentCompletedLessonItems.service";

import { QuestionsController } from "./api/questions.controller";
import { QuestionsRepository } from "./questions.repository";
import { QuestionsService } from "./questions.service";

@Module({
imports: [],
controllers: [QuestionsController],
providers: [QuestionsService, StudentCompletedLessonItemsService, LessonsRepository],
providers: [
QuestionsService,
StudentCompletedLessonItemsService,
QuestionsRepository,
LessonsRepository,
],
exports: [],
})
export class QuestionsModule {}
146 changes: 146 additions & 0 deletions apps/api/src/questions/questions.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { Inject, Injectable } from "@nestjs/common";
import { and, eq, inArray, sql } from "drizzle-orm";

import { DatabasePg, type UUIDType } from "src/common";
import {
lessonItems,
lessons,
questionAnswerOptions,
questions,
studentQuestionAnswers,
} from "src/storage/schema";

import type { AnswerQuestionSchema, QuestionSchema } from "./schema/question.schema";
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js";
import type * as schema from "src/storage/schema";

@Injectable()
export class QuestionsRepository {
constructor(@Inject("DB") private readonly db: DatabasePg) {}

async fetchQuestionData(
answerQuestion: AnswerQuestionSchema,
trx?: PostgresJsDatabase<typeof schema>,
): Promise<QuestionSchema> {
const dbInstance = trx ?? this.db;

const [questionData] = await dbInstance
.select({
lessonId: lessons.id,
questionId: sql<string>`${questions.id}`,
questionType: sql<string>`${questions.questionType}`,
lessonItemAssociationId: lessonItems.id,
})
.from(lessons)
.innerJoin(
lessonItems,
and(
eq(lessonItems.lessonId, answerQuestion.lessonId),
eq(lessonItems.lessonItemId, answerQuestion.questionId),
),
)
.leftJoin(
questions,
and(
eq(questions.id, lessonItems.lessonItemId),
eq(questions.archived, false),
eq(questions.state, "published"),
),
)
.where(
and(
eq(lessons.id, lessonItems.lessonId),
eq(lessons.archived, false),
eq(lessons.state, "published"),
),
);

return questionData;
}

async findExistingAnswer(
userId: UUIDType,
questionId: UUIDType,
lessonId: UUIDType,
courseId: UUIDType,
trx?: PostgresJsDatabase<typeof schema>,
): Promise<UUIDType | null> {
const dbInstance = trx ?? this.db;
const [existingAnswer] = await dbInstance
.select({
id: studentQuestionAnswers.id,
})
.from(studentQuestionAnswers)
.where(
and(
eq(studentQuestionAnswers.studentId, userId),
eq(studentQuestionAnswers.questionId, questionId),
eq(studentQuestionAnswers.lessonId, lessonId),
eq(studentQuestionAnswers.courseId, courseId),
),
);

return existingAnswer?.id;
}

async getQuestionAnswers(
questionId: UUIDType,
answerList: string[],
trx?: PostgresJsDatabase<typeof schema>,
) {
const dbInstance = trx ?? this.db;

return await dbInstance
.select({
answer: questionAnswerOptions.optionText,
})
.from(questionAnswerOptions)
.where(
and(
eq(questionAnswerOptions.questionId, questionId),
inArray(questionAnswerOptions.id, answerList),
),
);
}

async deleteAnswer(answerId: UUIDType, trx?: PostgresJsDatabase<typeof schema>) {
const dbInstance = trx ?? this.db;

return await dbInstance
.delete(studentQuestionAnswers)
.where(eq(studentQuestionAnswers.id, answerId));
}

async upsertAnswer(
courseId: UUIDType,
lessonId: UUIDType,
questionId: UUIDType,
userId: UUIDType,
answerId: UUIDType | null,
answer: string[],
trx?: PostgresJsDatabase<typeof schema>,
): Promise<void> {
const jsonBuildObjectArgs = answer.join(",");
const dbInstance = trx ?? this.db;

if (answerId) {
await dbInstance
.update(studentQuestionAnswers)
.set({
answer: sql`json_build_object(${sql.raw(jsonBuildObjectArgs)})`,
})
.where(eq(studentQuestionAnswers.id, answerId));
return;
}

await dbInstance.insert(studentQuestionAnswers).values({
questionId,
answer: sql`json_build_object(${sql.raw(jsonBuildObjectArgs)})`,
studentId: userId,
lessonId,
courseId,
});

return;
}
}
Loading

0 comments on commit 781c58e

Please sign in to comment.