Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add student favourtie course endpoint #84

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { StagingGuard } from "./common/guards/staging.guard";
import { HealthModule } from "./health/health.module";
import { ScheduleModule } from "@nestjs/schedule";
import { CoursesModule } from "./courses/courses.module";
import { StudentFavouritedCoursesModule } from "./studentFavouritedCourses/studentFavouritedCourses.module";

@Module({
imports: [
Expand Down Expand Up @@ -62,6 +63,7 @@ import { CoursesModule } from "./courses/courses.module";
(env) => env.NODE_ENV !== "test",
),
CoursesModule,
StudentFavouritedCoursesModule,
],
controllers: [],
providers: [
Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/courses/courses.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
users,
} from "../storage/schema";
import { getSortOptions } from "src/common/helpers/getSortOptions";
import { Status } from "src/storage/schema/utils";

@Injectable()
export class CoursesService {
Expand Down Expand Up @@ -170,7 +171,7 @@ export class CoursesService {
}

private getFiltersConditions(filters: CoursesFilterSchema) {
const conditions = [eq(courses.state, "published")];
const conditions = [eq(courses.state, Status.published.key)];
if (filters.title) {
conditions.push(
like(categories.title, `%${filters.title.toLowerCase()}%`),
Expand Down
4 changes: 2 additions & 2 deletions apps/api/src/storage/schema/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,6 @@ export const timestamps = {
export const archived = boolean("archived").default(false).notNull();

export const Status = {
draft: "Draft",
published: "Published",
draft: { key: "draft", value: "Draft" },
published: { key: "published", value: "Published" },
} as const;
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Body, Controller, Delete, Post, Query } from "@nestjs/common";
import { Validate } from "nestjs-typebox";
import { BaseResponse, nullResponse, UUIDSchema } from "src/common";
import { CurrentUser } from "src/common/decorators/user.decorator";
import { StudentFavouritedCoursesService } from "../studentFavouritedCourses.service";
import {
createFavouritedCourseSchema,
CreateFavouritedCourseSchema,
} from "../schemas/createFavouritedCourse.schema";

@Controller("studentFavouritedCourses")
export class StudentFavouritedCoursesController {
constructor(
private readonly studentFavouritedCoursesService: StudentFavouritedCoursesService,
) {}

@Post()
@Validate({
request: [{ type: "body", schema: createFavouritedCourseSchema }],
})
async createFavouritedCourse(
@Body() data: CreateFavouritedCourseSchema,
@CurrentUser() currentUser: { userId: string },
): Promise<BaseResponse<{ message: string }>> {
await this.studentFavouritedCoursesService.createFavouritedCourseForUser(
data.courseId,
currentUser.userId,
);

return new BaseResponse({
message: "Favourite course created successfully",
});
}

@Delete()
@Validate({
response: nullResponse(),
request: [{ type: "query", name: "id", schema: UUIDSchema }],
})
async deleteFavouritedCourseForUser(
@Query("id") id: string,
@CurrentUser() currentUser: { userId: string },
): Promise<null> {
console.log(id, currentUser.userId);

await this.studentFavouritedCoursesService.deleteFavouritedCourseForUser(
id,
currentUser.userId,
);

return null;
}
}
16 changes: 16 additions & 0 deletions apps/api/src/studentFavouritedCourses/schemas/course.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Type, Static } from "@sinclair/typebox";
import { UUIDSchema } from "src/common";

export const allCoursesSchema = Type.Array(
Type.Object({
id: UUIDSchema,
title: Type.String(),
imageUrl: Type.Union([Type.String(), Type.Null()]),
author: Type.String(),
category: Type.String(),
courseLessonCount: Type.Number(),
enrolledParticipantCount: Type.Number(),
}),
);

export type AllCoursesResponse = Static<typeof allCoursesSchema>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Static, Type } from "@sinclair/typebox";
import { UUIDSchema } from "src/common";

export const createFavouritedCourseSchema = Type.Object({
courseId: UUIDSchema,
});

export type CreateFavouritedCourseSchema = Static<
typeof createFavouritedCourseSchema
>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from "@nestjs/common";

import { StudentFavouritedCoursesService } from "./studentFavouritedCourses.service";
import { StudentFavouritedCoursesController } from "./api/studentFavouritedCourses.controller";

@Module({
imports: [],
controllers: [StudentFavouritedCoursesController],
providers: [StudentFavouritedCoursesService],
exports: [],
})
export class StudentFavouritedCoursesModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {
ConflictException,
Inject,
Injectable,
NotFoundException,
} from "@nestjs/common";
import { and, eq } from "drizzle-orm";
import { DatabasePg } from "src/common";
import { courses, studentFavouritedCourses } from "src/storage/schema";
import { Status } from "src/storage/schema/utils";

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

async createFavouritedCourseForUser(courseId: string, userId: string) {
const [course] = await this.db
.select()
.from(courses)
.where(
and(eq(courses.id, courseId), eq(courses.state, Status.published.key)),
);
if (!course) {
throw new NotFoundException("Course not found");
}

const [existingRecord] = await this.db
.select()
.from(studentFavouritedCourses)
.where(
and(
eq(studentFavouritedCourses.courseId, courseId),
eq(studentFavouritedCourses.studentId, userId),
),
);
if (existingRecord) {
throw new ConflictException("Favourite course already exists");
}

await this.db
.insert(studentFavouritedCourses)
.values({ courseId: courseId, studentId: userId })
.returning();
}

async deleteFavouritedCourseForUser(id: string, userId: string) {
const [deletedFavouritedCourse] = await this.db
.delete(studentFavouritedCourses)
.where(
and(
eq(studentFavouritedCourses.id, id),
eq(studentFavouritedCourses.studentId, userId),
),
)
.returning();

if (!deletedFavouritedCourse) {
throw new NotFoundException("Favourite course not found");
}
}
}
61 changes: 61 additions & 0 deletions apps/api/src/swagger/api-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -836,6 +836,52 @@
}
}
}
},
"/api/studentFavouritedCourses": {
"post": {
"operationId": "StudentFavouritedCoursesController_createFavouritedCourse",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateFavouritedCourseBody"
}
}
}
},
"responses": {
"201": {
"description": ""
}
}
},
"delete": {
"operationId": "StudentFavouritedCoursesController_deleteFavouritedCourseForUser",
"parameters": [
{
"name": "id",
"required": false,
"in": "query",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DeleteFavouritedCourseForUserResponse"
}
}
}
}
}
}
}
},
"info": {
Expand Down Expand Up @@ -1489,6 +1535,21 @@
"data",
"pagination"
]
},
"CreateFavouritedCourseBody": {
"type": "object",
"properties": {
"courseId": {
"format": "uuid",
"type": "string"
}
},
"required": [
"courseId"
]
},
"DeleteFavouritedCourseForUserResponse": {
"type": "null"
}
}
}
Expand Down
Loading