Skip to content

Commit

Permalink
feat: add student favourtie course endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
wielopolski committed Sep 12, 2024
1 parent 5363e6b commit 2c4c77f
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 32 deletions.
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
10 changes: 0 additions & 10 deletions apps/api/src/courses/helpers/index.ts

This file was deleted.

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");
}
}
}
94 changes: 75 additions & 19 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 @@ -1288,37 +1334,32 @@
"title": {
"type": "string"
},
"imageUrl": {
"archived": {
"anyOf": [
{
"type": "string"
"type": "boolean"
},
{
"type": "null"
}
]
},
"author": {
"type": "string"
},
"category": {
"type": "string"
},
"courseLessonCount": {
"type": "number"
},
"enrolledParticipantCount": {
"type": "number"
"createdAt": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"title",
"imageUrl",
"author",
"category",
"courseLessonCount",
"enrolledParticipantCount"
"archived",
"createdAt"
]
}
},
Expand Down Expand Up @@ -1494,7 +1535,22 @@
"data",
"pagination"
]
},
"CreateFavouritedCourseBody": {
"type": "object",
"properties": {
"courseId": {
"format": "uuid",
"type": "string"
}
},
"required": [
"courseId"
]
},
"DeleteFavouritedCourseForUserResponse": {
"type": "null"
}
}
}
}
}

0 comments on commit 2c4c77f

Please sign in to comment.