Skip to content

Commit

Permalink
feat: add student favourtie course list for student endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
wielopolski committed Sep 12, 2024
1 parent ee1a1e4 commit 555296a
Show file tree
Hide file tree
Showing 4 changed files with 243 additions and 10 deletions.
8 changes: 3 additions & 5 deletions apps/api/src/courses/courses.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,15 @@ export class CoursesService {
.leftJoin(studentCourses, eq(courses.id, studentCourses.courseId))
.leftJoin(courseLessons, eq(courses.id, courseLessons.courseId))
.where(and(...conditions))
.orderBy(sortOrder(this.getColumnToSortBy(sortedField)))
.groupBy(
courses.id,
courses.title,
courses.imageUrl,
users.firstName,
users.lastName,
categories.title,
);
console.log(sortOrder(this.getColumnToSortBy(sortedField)));
)
.orderBy(sortOrder(this.getColumnToSortBy(sortedField)));
const dynamicQuery = queryDB.$dynamic();
const paginatedQuery = addPagination(dynamicQuery, page, perPage);
const data = await paginatedQuery;
Expand All @@ -101,8 +100,7 @@ export class CoursesService {
users.firstName,
users.lastName,
categories.title,
)
.orderBy(sortOrder(this.getColumnToSortBy(sortedField)));
);

return {
data: data,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
import { Body, Controller, Delete, Post, Query } from "@nestjs/common";
import { Body, Controller, Delete, Get, Post, Query } from "@nestjs/common";
import { Validate } from "nestjs-typebox";
import { BaseResponse, nullResponse, UUIDSchema } from "src/common";
import {
BaseResponse,
nullResponse,
PaginatedResponse,
paginatedResponse,
UUIDSchema,
} from "src/common";
import { CurrentUser } from "src/common/decorators/user.decorator";
import { StudentFavouriteCoursesService } from "../studentFavouriteCourses.service";
import {
createFavouriteCourseSchema,
CreateFavouriteCourseSchema,
} from "../schemas/createFavouriteCourse.schema";
import { Type } from "@sinclair/typebox";
import { AllCoursesResponse, allCoursesSchema } from "../schemas/course.schema";
import {
SortCourseFieldsOptions,
sortCourseFieldsOptions,
} from "src/courses/schemas/courseQuery";

@Controller("studentFavouriteCourses")
export class StudentFavouriteCoursesController {
Expand All @@ -32,6 +44,32 @@ export class StudentFavouriteCoursesController {
});
}

@Get()
@Validate({
response: paginatedResponse(allCoursesSchema),
request: [
{ type: "query", name: "page", schema: Type.Number({ minimum: 1 }) },
{ type: "query", name: "perPage", schema: Type.Number() },
{ type: "query", name: "sort", schema: sortCourseFieldsOptions },
],
})
async getAllCategories(
@Query("page") page: number,
@Query("perPage") perPage: number,
@Query("sort") sort: SortCourseFieldsOptions,
@CurrentUser() currentUser: { userId: string },
): Promise<PaginatedResponse<AllCoursesResponse>> {
const query = { page, perPage, sort };

const data =
await this.studentFavouriteCoursesService.getCoursesForStudents(
query,
currentUser.userId,
);

return new PaginatedResponse(data);
}

@Delete()
@Validate({
response: nullResponse(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,25 @@ import {
Injectable,
NotFoundException,
} from "@nestjs/common";
import { and, eq } from "drizzle-orm";
import { DatabasePg } from "src/common";
import { courses, studentFavouriteCourses } from "src/storage/schema";
import { and, count, eq, sql } from "drizzle-orm";
import { DatabasePg, Pagination } from "src/common";
import { CoursesQuery } from "src/courses/api/courses.types";
import {
categories,
courseLessons,
courses,
studentCourses,
studentFavouriteCourses,
users,
} from "src/storage/schema";
import { Status } from "src/storage/schema/utils";
import { AllCoursesResponse } from "./schemas/course.schema";
import { addPagination, DEFAULT_PAGE_SIZE } from "src/common/pagination";
import {
CourseSortField,
CourseSortFields,
} from "src/courses/schemas/courseQuery";
import { getSortOptions } from "./helpers";

@Injectable()
export class StudentFavouriteCoursesService {
Expand Down Expand Up @@ -43,6 +58,81 @@ export class StudentFavouriteCoursesService {
.returning();
}

async getCoursesForStudents(
query: CoursesQuery,
userId: string,
): Promise<{ data: AllCoursesResponse; pagination: Pagination }> {
const {
sort = CourseSortFields.title,
perPage = DEFAULT_PAGE_SIZE,
page = 1,
} = query;

const { sortOrder, sortedField } = getSortOptions(sort);

const selectedColumns = {
id: courses.id,
creationDate: courses.createdAt,
title: courses.title,
imageUrl: courses.imageUrl,
author: sql<string>`CONCAT(${users.firstName} || ' ' || ${users.lastName})`,
category: categories.title,
courseLessonCount: sql<number>`(SELECT COUNT(*) FROM ${courseLessons} WHERE ${courseLessons.courseId} = ${courses.id})::INTEGER`,
enrolledParticipantCount: count(studentCourses.courseId),
};

return this.db.transaction(async (tx) => {
const queryDB = tx
.select(selectedColumns)
.from(studentFavouriteCourses)
.innerJoin(courses, eq(studentFavouriteCourses.courseId, courses.id))
.innerJoin(categories, eq(courses.categoryId, categories.id))
.leftJoin(users, eq(courses.authorId, users.id))
.leftJoin(studentCourses, eq(courses.id, studentCourses.courseId))
.leftJoin(courseLessons, eq(courses.id, courseLessons.courseId))
.where(and(eq(studentFavouriteCourses.studentId, userId)))
.orderBy(sortOrder(this.getColumnToSortBy(sortedField)))
.groupBy(
courses.id,
courses.title,
courses.imageUrl,
users.firstName,
users.lastName,
categories.title,
);

const dynamicQuery = queryDB.$dynamic();
const paginatedQuery = addPagination(dynamicQuery, page, perPage);
const data = await paginatedQuery;
const [totalItems] = await tx
.select({ count: count() })
.from(studentFavouriteCourses)
.innerJoin(courses, eq(studentFavouriteCourses.courseId, courses.id))
.innerJoin(categories, eq(courses.categoryId, categories.id))
.leftJoin(users, eq(courses.authorId, users.id))
.leftJoin(studentCourses, eq(courses.id, studentCourses.courseId))
.leftJoin(courseLessons, eq(courses.id, courseLessons.courseId))
.where(and(eq(studentFavouriteCourses.studentId, userId)))
.groupBy(
courses.id,
courses.title,
courses.imageUrl,
users.firstName,
users.lastName,
categories.title,
);

return {
data: data,
pagination: {
totalItems: totalItems?.count || 0,
page,
perPage,
},
};
});
}

async deleteFavouriteCourseForUser(id: string, userId: string) {
const [deletedFavouriteCourse] = await this.db
.delete(studentFavouriteCourses)
Expand All @@ -58,4 +148,21 @@ export class StudentFavouriteCoursesService {
throw new NotFoundException("Favourite course not found");
}
}

private getColumnToSortBy(sort: CourseSortField) {
switch (sort) {
case CourseSortFields.author:
return sql<string>`CONCAT(${users.firstName} || ' ' || ${users.lastName})`;
case CourseSortFields.category:
return categories.title;
case CourseSortFields.creationDate:
return courses.createdAt;
case CourseSortFields.lessonsCount:
return count(studentCourses.courseId);
case CourseSortFields.enrolledParticipantsCount:
return count(studentCourses.courseId);
default:
return courses.title;
}
}
}
90 changes: 90 additions & 0 deletions apps/api/src/swagger/api-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,96 @@
}
}
},
"get": {
"operationId": "StudentFavouriteCoursesController_getAllCategories",
"parameters": [
{
"name": "page",
"required": false,
"in": "query",
"schema": {
"minimum": 1,
"type": "number"
}
},
{
"name": "perPage",
"required": false,
"in": "query",
"schema": {
"type": "number"
}
},
{
"name": "sort",
"required": false,
"in": "query",
"schema": {
"anyOf": [
{
"const": "title",
"type": "string"
},
{
"const": "category",
"type": "string"
},
{
"const": "creationDate",
"type": "string"
},
{
"const": "author",
"type": "string"
},
{
"const": "lessonsCount",
"type": "string"
},
{
"const": "enrolledParticipantsCount",
"type": "string"
},
{
"const": "-title",
"type": "string"
},
{
"const": "-category",
"type": "string"
},
{
"const": "-creationDate",
"type": "string"
},
{
"const": "-author",
"type": "string"
},
{
"const": "-lessonsCount",
"type": "string"
},
{
"const": "-enrolledParticipantsCount",
"type": "string"
}
]
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/GetAllCategoriesResponse"
}
}
}
}
}
},
"delete": {
"operationId": "StudentFavouriteCoursesController_deleteFavouriteCourseForUser",
"parameters": [
Expand Down

0 comments on commit 555296a

Please sign in to comment.