From d8148094cea1370b88d7d836f915310dc36faef3 Mon Sep 17 00:00:00 2001 From: Akshat-Kalra Date: Wed, 28 May 2025 16:40:55 -0700 Subject: [PATCH 1/5] Add Swagger documentation setup and update route parameters in controllers --- packages/server/src/bootstrap.ts | 11 +++++++++++ packages/server/src/course/course.controller.ts | 4 ++-- .../src/organization/organization.controller.ts | 8 ++++---- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/server/src/bootstrap.ts b/packages/server/src/bootstrap.ts index ef28b5a74..3029452e6 100644 --- a/packages/server/src/bootstrap.ts +++ b/packages/server/src/bootstrap.ts @@ -10,6 +10,7 @@ import * as expressSession from 'express-session'; import { ApplicationConfigService } from './config/application_config.service'; import { NestExpressApplication } from '@nestjs/platform-express'; import { Chromiumly } from 'chromiumly'; +import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export async function bootstrap(hot: any): Promise { @@ -58,6 +59,16 @@ export async function bootstrap(hot: any): Promise { Chromiumly.configure({ endpoint: 'http://localhost:3004' }); app.set('trust proxy', 'loopback'); // Trust requests from the loopback address + const config = new DocumentBuilder() + .setTitle('HelpMe API') + .setDescription('HelpMe API doc') + .setVersion('1.0') + .addTag('cats') + .build(); + const documentFactory = () => SwaggerModule.createDocument(app, config); + if (process.env.NODE_ENV === 'development') { + SwaggerModule.setup('api', app, documentFactory); + } await app.listen(3002); if (hot) { diff --git a/packages/server/src/course/course.controller.ts b/packages/server/src/course/course.controller.ts index 956ff9b48..9833de0bf 100644 --- a/packages/server/src/course/course.controller.ts +++ b/packages/server/src/course/course.controller.ts @@ -714,13 +714,13 @@ export class CourseController { } } - @Get(':id/get_user_info/:page/:role?') + @Get(':id/get_user_info/:page') @UseGuards(JwtAuthGuard, CourseRolesGuard, EmailVerifiedGuard) @Roles(Role.PROFESSOR) async getUserInfo( @Param('id', ParseIntPipe) courseId: number, @Param('page', ParseIntPipe) page: number, - @Param('role') role?: Role | 'staff', + @Query('role') role?: Role | 'staff', @Query('search') search?: string, ): Promise { const pageSize = role === 'staff' ? 100 : 50; diff --git a/packages/server/src/organization/organization.controller.ts b/packages/server/src/organization/organization.controller.ts index 7639adaee..9cce95081 100644 --- a/packages/server/src/organization/organization.controller.ts +++ b/packages/server/src/organization/organization.controller.ts @@ -1451,12 +1451,12 @@ export class OrganizationController { return res.status(HttpStatus.OK).send(userInfo); } - @Get(':oid/get_users/:page?') + @Get(':oid/get_users') @UseGuards(JwtAuthGuard, OrganizationRolesGuard, EmailVerifiedGuard) @Roles(OrganizationRole.ADMIN) async getUsers( @Param('oid', ParseIntPipe) oid: number, - @Param('page', ParseIntPipe) page: number, + @Query('page', ParseIntPipe) page: number, @Query('search') search: string, ): Promise { const pageSize = 50; @@ -1468,12 +1468,12 @@ export class OrganizationController { return await this.organizationService.getUsers(oid, page, pageSize, search); } - @Get(':oid/get_courses/:page?') + @Get(':oid/get_courses') @UseGuards(JwtAuthGuard, OrganizationRolesGuard, EmailVerifiedGuard) @Roles(OrganizationRole.ADMIN) async getCourses( @Param('oid', ParseIntPipe) oid: number, - @Param('page', new DefaultValuePipe(-1), ParseIntPipe) page: number, + @Query('page', new DefaultValuePipe(-1), ParseIntPipe) page: number, @Query('search') search: string, ): Promise { const pageSize = 50; From a9d57530f03e2cb464362f3c0cdc47254c06688d Mon Sep 17 00:00:00 2001 From: Akshat-Kalra Date: Wed, 28 May 2025 16:52:18 -0700 Subject: [PATCH 2/5] Add @nestjs/swagger dependency for API documentation --- packages/server/package.json | 1 + yarn.lock | 55 ++++++++++++++++++++++++++++++------ 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/packages/server/package.json b/packages/server/package.json index 78fd2d676..2cd5f74e6 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -46,6 +46,7 @@ "@nestjs/passport": "^7.1.0", "@nestjs/platform-express": "^7.5.0", "@nestjs/schedule": "^0.4.0", + "@nestjs/swagger": "^11.2.0", "@nestjs/throttler": "^6.4.0", "@nestjs/typeorm": "^11.0.0", "@sentry/cli": "^2.36.1", diff --git a/yarn.lock b/yarn.lock index c0a34883b..c44109272 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2128,6 +2128,11 @@ semver "^7.3.5" tar "^6.1.11" +"@microsoft/tsdoc@0.15.1": + version "0.15.1" + resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz#d4f6937353bc4568292654efb0a0e0532adbcba2" + integrity sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw== + "@nest-modules/mailer@^1.3.22": version "1.3.22" resolved "https://registry.yarnpkg.com/@nest-modules/mailer/-/mailer-1.3.22.tgz#45cf18ddde2d9ef4521ef7ecf67af01a0de020dd" @@ -2230,6 +2235,11 @@ "@types/jsonwebtoken" "8.5.0" jsonwebtoken "8.5.1" +"@nestjs/mapped-types@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz#b9b536b7c3571567aa1d0223db8baa1a51505a19" + integrity sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw== + "@nestjs/passport@^7.1.0": version "7.1.6" resolved "https://registry.yarnpkg.com/@nestjs/passport/-/passport-7.1.6.tgz#608bb265dfd85602aa74bbceacc012ffb2e9d25d" @@ -2265,6 +2275,18 @@ jsonc-parser "3.0.0" pluralize "8.0.0" +"@nestjs/swagger@^11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@nestjs/swagger/-/swagger-11.2.0.tgz#a1b10620a9f90c78edf897a9386dc4f3e014387e" + integrity sha512-5wolt8GmpNcrQv34tIPUtPoV1EeFbCetm40Ij3+M0FNNnf2RJ3FyWfuQvI8SBlcJyfaounYVTKzKHreFXsUyOg== + dependencies: + "@microsoft/tsdoc" "0.15.1" + "@nestjs/mapped-types" "2.1.0" + js-yaml "4.1.0" + lodash "4.17.21" + path-to-regexp "8.2.0" + swagger-ui-dist "5.21.0" + "@nestjs/testing@^10.0.0": version "10.4.17" resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-10.4.17.tgz#f3a19309768261a380ac96ecddcef81231019e70" @@ -3141,6 +3163,11 @@ resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.11.0.tgz#75dce8e972f90bba488e2b0cc677fb233aa357ab" integrity sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ== +"@scarf/scarf@=1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.4.0.tgz#3bbb984085dbd6d982494538b523be1ce6562972" + integrity sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ== + "@schematics/schematics@0.1102.6": version "0.1102.6" resolved "https://registry.yarnpkg.com/@schematics/schematics/-/schematics-0.1102.6.tgz#2ce02f7c11558471628eafeb34faaa7f5ab4b22c" @@ -10431,6 +10458,13 @@ js-stringify@^1.0.1, js-stringify@^1.0.2: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== +js-yaml@4.1.0, js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + js-yaml@^3.13.1: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" @@ -10439,13 +10473,6 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - jsbn@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" @@ -11032,7 +11059,7 @@ lodash.toarray@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" integrity sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw== -lodash@^4, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4: +lodash@4.17.21, lodash@^4, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -13114,6 +13141,11 @@ path-to-regexp@3.3.0: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-3.3.0.tgz#f7f31d32e8518c2660862b644414b6d5c63a611b" integrity sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw== +path-to-regexp@8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-8.2.0.tgz#73990cc29e57a3ff2a0d914095156df5db79e8b4" + integrity sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ== + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -16118,6 +16150,13 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +swagger-ui-dist@5.21.0: + version "5.21.0" + resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-5.21.0.tgz#aed230fe6e294c9470217e67697d601e3bb8eb9d" + integrity sha512-E0K3AB6HvQd8yQNSMR7eE5bk+323AUxjtCz/4ZNKiahOlPhPJxqn3UPIGs00cyY/dhrTDJ61L7C/a8u6zhGrZg== + dependencies: + "@scarf/scarf" "=1.4.0" + swr@^2.2.5: version "2.3.3" resolved "https://registry.yarnpkg.com/swr/-/swr-2.3.3.tgz#9d6a703355f15f9099f45114db3ef75764444788" From 04032372b48b729eac8a196689dfd2966e40401a Mon Sep 17 00:00:00 2001 From: Akshat Date: Wed, 28 May 2025 19:28:35 -0700 Subject: [PATCH 3/5] Update packages/server/src/bootstrap.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/server/src/bootstrap.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/bootstrap.ts b/packages/server/src/bootstrap.ts index 3029452e6..a9e724793 100644 --- a/packages/server/src/bootstrap.ts +++ b/packages/server/src/bootstrap.ts @@ -65,9 +65,9 @@ export async function bootstrap(hot: any): Promise { .setVersion('1.0') .addTag('cats') .build(); - const documentFactory = () => SwaggerModule.createDocument(app, config); + const document = SwaggerModule.createDocument(app, config); if (process.env.NODE_ENV === 'development') { - SwaggerModule.setup('api', app, documentFactory); + SwaggerModule.setup('api', app, document); } await app.listen(3002); From a5aa0db0cb4e93d13040a458cb8d6213a92aeb52 Mon Sep 17 00:00:00 2001 From: Akshat Date: Wed, 28 May 2025 19:28:45 -0700 Subject: [PATCH 4/5] Update packages/server/src/organization/organization.controller.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/server/src/organization/organization.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/organization/organization.controller.ts b/packages/server/src/organization/organization.controller.ts index 9cce95081..bb2e8ee2e 100644 --- a/packages/server/src/organization/organization.controller.ts +++ b/packages/server/src/organization/organization.controller.ts @@ -1456,7 +1456,7 @@ export class OrganizationController { @Roles(OrganizationRole.ADMIN) async getUsers( @Param('oid', ParseIntPipe) oid: number, - @Query('page', ParseIntPipe) page: number, + @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number, @Query('search') search: string, ): Promise { const pageSize = 50; From 6b959af7f0ad3e630ebd0264b6ddbc9fbeb298a6 Mon Sep 17 00:00:00 2001 From: Akshat Date: Thu, 29 May 2025 15:24:26 -0700 Subject: [PATCH 5/5] Refactor organization integration tests to remove optional page parameter from GET /organization/:oid/get_users and GET /organization/:oid/get_courses routes --- .../test/__snapshots__/organization.integration.ts.snap | 4 ++-- packages/server/test/organization.integration.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/server/test/__snapshots__/organization.integration.ts.snap b/packages/server/test/__snapshots__/organization.integration.ts.snap index 7c77ab2e5..916befd97 100644 --- a/packages/server/test/__snapshots__/organization.integration.ts.snap +++ b/packages/server/test/__snapshots__/organization.integration.ts.snap @@ -45,7 +45,7 @@ exports[`Organization Integration GET /organization/:oid should return 200 and r } `; -exports[`Organization Integration GET /organization/:oid/get_courses/:page? should return 200 when user is an admin 1`] = ` +exports[`Organization Integration GET /organization/:oid/get_courses should return 200 when user is an admin 1`] = ` [ { "courseId": 1, @@ -84,7 +84,7 @@ exports[`Organization Integration GET /organization/:oid/get_user/:uid should re } `; -exports[`Organization Integration GET /organization/:oid/get_users/:page? should return 200 when user is an admin 1`] = ` +exports[`Organization Integration GET /organization/:oid/get_users should return 200 when user is an admin 1`] = ` [ { "email": "user@ubc.ca", diff --git a/packages/server/test/organization.integration.ts b/packages/server/test/organization.integration.ts index 1d47757da..d00c4913d 100644 --- a/packages/server/test/organization.integration.ts +++ b/packages/server/test/organization.integration.ts @@ -135,7 +135,7 @@ describe('Organization Integration', () => { }); }); - describe('GET /organization/:oid/get_users/:page?', () => { + describe('GET /organization/:oid/get_users', () => { it('should return 403 when user is not logged in', async () => { const organization = await OrganizationFactory.create(); const response = await supertest().get( @@ -180,7 +180,7 @@ describe('Organization Integration', () => { }); }); - describe('GET /organization/:oid/get_courses/:page?', () => { + describe('GET /organization/:oid/get_courses', () => { it('should return 403 when user is not logged in', async () => { const organization = await OrganizationFactory.create(); const response = await supertest().get(