From c69f739692274bd8d5ee7e1c40198480924eb06f Mon Sep 17 00:00:00 2001 From: ganthepro Date: Fri, 8 Nov 2024 18:56:49 +0700 Subject: [PATCH 001/155] Initialize project with NestJS framework and basic app structure --- dist/app.controller.d.ts | 6 ++++++ dist/app.controller.js | 34 ++++++++++++++++++++++++++++++++++ dist/app.controller.js.map | 1 + dist/app.module.d.ts | 2 ++ dist/app.module.js | 23 +++++++++++++++++++++++ dist/app.module.js.map | 1 + dist/app.service.d.ts | 3 +++ dist/app.service.js | 20 ++++++++++++++++++++ dist/app.service.js.map | 1 + dist/main.d.ts | 1 + dist/main.js | 10 ++++++++++ dist/main.js.map | 1 + src/app.controller.spec.ts | 24 ++++++++++++------------ src/app.controller.ts | 10 +++++----- src/app.module.ts | 8 ++++---- src/app.service.ts | 6 +++--- src/main.ts | 4 ++-- 17 files changed, 129 insertions(+), 26 deletions(-) create mode 100644 dist/app.controller.d.ts create mode 100644 dist/app.controller.js create mode 100644 dist/app.controller.js.map create mode 100644 dist/app.module.d.ts create mode 100644 dist/app.module.js create mode 100644 dist/app.module.js.map create mode 100644 dist/app.service.d.ts create mode 100644 dist/app.service.js create mode 100644 dist/app.service.js.map create mode 100644 dist/main.d.ts create mode 100644 dist/main.js create mode 100644 dist/main.js.map diff --git a/dist/app.controller.d.ts b/dist/app.controller.d.ts new file mode 100644 index 0000000..3859d69 --- /dev/null +++ b/dist/app.controller.d.ts @@ -0,0 +1,6 @@ +import { AppService } from './app.service'; +export declare class AppController { + private readonly appService; + constructor(appService: AppService); + getHello(): string; +} diff --git a/dist/app.controller.js b/dist/app.controller.js new file mode 100644 index 0000000..8ed6dc0 --- /dev/null +++ b/dist/app.controller.js @@ -0,0 +1,34 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AppController = void 0; +const common_1 = require("@nestjs/common"); +const app_service_1 = require("./app.service"); +let AppController = class AppController { + constructor(appService) { + this.appService = appService; + } + getHello() { + return this.appService.getHello(); + } +}; +exports.AppController = AppController; +__decorate([ + (0, common_1.Get)(), + __metadata("design:type", Function), + __metadata("design:paramtypes", []), + __metadata("design:returntype", String) +], AppController.prototype, "getHello", null); +exports.AppController = AppController = __decorate([ + (0, common_1.Controller)(), + __metadata("design:paramtypes", [app_service_1.AppService]) +], AppController); +//# sourceMappingURL=app.controller.js.map \ No newline at end of file diff --git a/dist/app.controller.js.map b/dist/app.controller.js.map new file mode 100644 index 0000000..b7d8131 --- /dev/null +++ b/dist/app.controller.js.map @@ -0,0 +1 @@ +{"version":3,"file":"app.controller.js","sourceRoot":"","sources":["../src/app.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAiD;AACjD,+CAA2C;AAGpC,IAAM,aAAa,GAAnB,MAAM,aAAa;IACtB,YAA6B,UAAsB;QAAtB,eAAU,GAAV,UAAU,CAAY;IAAI,CAAC;IAGxD,QAAQ;QACJ,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;IACtC,CAAC;CACJ,CAAA;AAPY,sCAAa;AAItB;IADC,IAAA,YAAG,GAAE;;;;6CAGL;wBANQ,aAAa;IADzB,IAAA,mBAAU,GAAE;qCAEgC,wBAAU;GAD1C,aAAa,CAOzB"} \ No newline at end of file diff --git a/dist/app.module.d.ts b/dist/app.module.d.ts new file mode 100644 index 0000000..09cdb35 --- /dev/null +++ b/dist/app.module.d.ts @@ -0,0 +1,2 @@ +export declare class AppModule { +} diff --git a/dist/app.module.js b/dist/app.module.js new file mode 100644 index 0000000..25985e8 --- /dev/null +++ b/dist/app.module.js @@ -0,0 +1,23 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AppModule = void 0; +const common_1 = require("@nestjs/common"); +const app_controller_1 = require("./app.controller"); +const app_service_1 = require("./app.service"); +let AppModule = class AppModule { +}; +exports.AppModule = AppModule; +exports.AppModule = AppModule = __decorate([ + (0, common_1.Module)({ + imports: [], + controllers: [app_controller_1.AppController], + providers: [app_service_1.AppService], + }) +], AppModule); +//# sourceMappingURL=app.module.js.map \ No newline at end of file diff --git a/dist/app.module.js.map b/dist/app.module.js.map new file mode 100644 index 0000000..565e423 --- /dev/null +++ b/dist/app.module.js.map @@ -0,0 +1 @@ +{"version":3,"file":"app.module.js","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,qDAAiD;AACjD,+CAA2C;AAOpC,IAAM,SAAS,GAAf,MAAM,SAAS;CAAI,CAAA;AAAb,8BAAS;oBAAT,SAAS;IALrB,IAAA,eAAM,EAAC;QACJ,OAAO,EAAE,EAAE;QACX,WAAW,EAAE,CAAC,8BAAa,CAAC;QAC5B,SAAS,EAAE,CAAC,wBAAU,CAAC;KAC1B,CAAC;GACW,SAAS,CAAI"} \ No newline at end of file diff --git a/dist/app.service.d.ts b/dist/app.service.d.ts new file mode 100644 index 0000000..0496e79 --- /dev/null +++ b/dist/app.service.d.ts @@ -0,0 +1,3 @@ +export declare class AppService { + getHello(): string; +} diff --git a/dist/app.service.js b/dist/app.service.js new file mode 100644 index 0000000..3bfb90b --- /dev/null +++ b/dist/app.service.js @@ -0,0 +1,20 @@ +"use strict"; +var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AppService = void 0; +const common_1 = require("@nestjs/common"); +let AppService = class AppService { + getHello() { + return 'Hello World!'; + } +}; +exports.AppService = AppService; +exports.AppService = AppService = __decorate([ + (0, common_1.Injectable)() +], AppService); +//# sourceMappingURL=app.service.js.map \ No newline at end of file diff --git a/dist/app.service.js.map b/dist/app.service.js.map new file mode 100644 index 0000000..c99d561 --- /dev/null +++ b/dist/app.service.js.map @@ -0,0 +1 @@ +{"version":3,"file":"app.service.js","sourceRoot":"","sources":["../src/app.service.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA4C;AAGrC,IAAM,UAAU,GAAhB,MAAM,UAAU;IACnB,QAAQ;QACJ,OAAO,cAAc,CAAC;IAC1B,CAAC;CACJ,CAAA;AAJY,gCAAU;qBAAV,UAAU;IADtB,IAAA,mBAAU,GAAE;GACA,UAAU,CAItB"} \ No newline at end of file diff --git a/dist/main.d.ts b/dist/main.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/dist/main.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/dist/main.js b/dist/main.js new file mode 100644 index 0000000..274552e --- /dev/null +++ b/dist/main.js @@ -0,0 +1,10 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +const core_1 = require("@nestjs/core"); +const app_module_1 = require("./app.module"); +async function bootstrap() { + const app = await core_1.NestFactory.create(app_module_1.AppModule); + await app.listen(process.env.PORT ?? 3000); +} +bootstrap(); +//# sourceMappingURL=main.js.map \ No newline at end of file diff --git a/dist/main.js.map b/dist/main.js.map new file mode 100644 index 0000000..f7a5f41 --- /dev/null +++ b/dist/main.js.map @@ -0,0 +1 @@ +{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";;AAAA,uCAA2C;AAC3C,6CAAyC;AAEzC,KAAK,UAAU,SAAS;IACpB,MAAM,GAAG,GAAG,MAAM,kBAAW,CAAC,MAAM,CAAC,sBAAS,CAAC,CAAC;IAChD,MAAM,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;AAC/C,CAAC;AACD,SAAS,EAAE,CAAC"} \ No newline at end of file diff --git a/src/app.controller.spec.ts b/src/app.controller.spec.ts index d22f389..2552ec5 100644 --- a/src/app.controller.spec.ts +++ b/src/app.controller.spec.ts @@ -3,20 +3,20 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { - let appController: AppController; + let appController: AppController; - beforeEach(async () => { - const app: TestingModule = await Test.createTestingModule({ - controllers: [AppController], - providers: [AppService], - }).compile(); + beforeEach(async () => { + const app: TestingModule = await Test.createTestingModule({ + controllers: [AppController], + providers: [AppService], + }).compile(); - appController = app.get(AppController); - }); + appController = app.get(AppController); + }); - describe('root', () => { - it('should return "Hello World!"', () => { - expect(appController.getHello()).toBe('Hello World!'); + describe('root', () => { + it('should return "Hello World!"', () => { + expect(appController.getHello()).toBe('Hello World!'); + }); }); - }); }); diff --git a/src/app.controller.ts b/src/app.controller.ts index cce879e..edfc45e 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -3,10 +3,10 @@ import { AppService } from './app.service'; @Controller() export class AppController { - constructor(private readonly appService: AppService) {} + constructor(private readonly appService: AppService) { } - @Get() - getHello(): string { - return this.appService.getHello(); - } + @Get() + getHello(): string { + return this.appService.getHello(); + } } diff --git a/src/app.module.ts b/src/app.module.ts index 8662803..7cec292 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -3,8 +3,8 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ - imports: [], - controllers: [AppController], - providers: [AppService], + imports: [], + controllers: [AppController], + providers: [AppService], }) -export class AppModule {} +export class AppModule { } diff --git a/src/app.service.ts b/src/app.service.ts index 927d7cc..61b7a5b 100644 --- a/src/app.service.ts +++ b/src/app.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { - getHello(): string { - return 'Hello World!'; - } + getHello(): string { + return 'Hello World!'; + } } diff --git a/src/main.ts b/src/main.ts index f76bc8d..484c87b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,7 +2,7 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; async function bootstrap() { - const app = await NestFactory.create(AppModule); - await app.listen(process.env.PORT ?? 3000); + const app = await NestFactory.create(AppModule); + await app.listen(process.env.PORT ?? 3000); } bootstrap(); From 4a9da47cc28f74b68e806c968e98d8c3d2f16c36 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Fri, 8 Nov 2024 18:57:30 +0700 Subject: [PATCH 002/155] chore: Add /dist to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 12fb34f..8071ebf 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ node_modules/ /.project /.settings *.code-workspace +/dist # Vim [._]*.s[a-v][a-z] From f90ba9e684c1caada879f360617b3acaf4752388 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Fri, 8 Nov 2024 18:58:12 +0700 Subject: [PATCH 003/155] chore: Remove unnecessary files from /dist folder --- dist/app.controller.d.ts | 6 ------ dist/app.controller.js | 34 ---------------------------------- dist/app.controller.js.map | 1 - dist/app.module.d.ts | 2 -- dist/app.module.js | 23 ----------------------- dist/app.module.js.map | 1 - dist/app.service.d.ts | 3 --- dist/app.service.js | 20 -------------------- dist/app.service.js.map | 1 - dist/main.d.ts | 1 - dist/main.js | 10 ---------- dist/main.js.map | 1 - 12 files changed, 103 deletions(-) delete mode 100644 dist/app.controller.d.ts delete mode 100644 dist/app.controller.js delete mode 100644 dist/app.controller.js.map delete mode 100644 dist/app.module.d.ts delete mode 100644 dist/app.module.js delete mode 100644 dist/app.module.js.map delete mode 100644 dist/app.service.d.ts delete mode 100644 dist/app.service.js delete mode 100644 dist/app.service.js.map delete mode 100644 dist/main.d.ts delete mode 100644 dist/main.js delete mode 100644 dist/main.js.map diff --git a/dist/app.controller.d.ts b/dist/app.controller.d.ts deleted file mode 100644 index 3859d69..0000000 --- a/dist/app.controller.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { AppService } from './app.service'; -export declare class AppController { - private readonly appService; - constructor(appService: AppService); - getHello(): string; -} diff --git a/dist/app.controller.js b/dist/app.controller.js deleted file mode 100644 index 8ed6dc0..0000000 --- a/dist/app.controller.js +++ /dev/null @@ -1,34 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.AppController = void 0; -const common_1 = require("@nestjs/common"); -const app_service_1 = require("./app.service"); -let AppController = class AppController { - constructor(appService) { - this.appService = appService; - } - getHello() { - return this.appService.getHello(); - } -}; -exports.AppController = AppController; -__decorate([ - (0, common_1.Get)(), - __metadata("design:type", Function), - __metadata("design:paramtypes", []), - __metadata("design:returntype", String) -], AppController.prototype, "getHello", null); -exports.AppController = AppController = __decorate([ - (0, common_1.Controller)(), - __metadata("design:paramtypes", [app_service_1.AppService]) -], AppController); -//# sourceMappingURL=app.controller.js.map \ No newline at end of file diff --git a/dist/app.controller.js.map b/dist/app.controller.js.map deleted file mode 100644 index b7d8131..0000000 --- a/dist/app.controller.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"app.controller.js","sourceRoot":"","sources":["../src/app.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAiD;AACjD,+CAA2C;AAGpC,IAAM,aAAa,GAAnB,MAAM,aAAa;IACtB,YAA6B,UAAsB;QAAtB,eAAU,GAAV,UAAU,CAAY;IAAI,CAAC;IAGxD,QAAQ;QACJ,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;IACtC,CAAC;CACJ,CAAA;AAPY,sCAAa;AAItB;IADC,IAAA,YAAG,GAAE;;;;6CAGL;wBANQ,aAAa;IADzB,IAAA,mBAAU,GAAE;qCAEgC,wBAAU;GAD1C,aAAa,CAOzB"} \ No newline at end of file diff --git a/dist/app.module.d.ts b/dist/app.module.d.ts deleted file mode 100644 index 09cdb35..0000000 --- a/dist/app.module.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -export declare class AppModule { -} diff --git a/dist/app.module.js b/dist/app.module.js deleted file mode 100644 index 25985e8..0000000 --- a/dist/app.module.js +++ /dev/null @@ -1,23 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.AppModule = void 0; -const common_1 = require("@nestjs/common"); -const app_controller_1 = require("./app.controller"); -const app_service_1 = require("./app.service"); -let AppModule = class AppModule { -}; -exports.AppModule = AppModule; -exports.AppModule = AppModule = __decorate([ - (0, common_1.Module)({ - imports: [], - controllers: [app_controller_1.AppController], - providers: [app_service_1.AppService], - }) -], AppModule); -//# sourceMappingURL=app.module.js.map \ No newline at end of file diff --git a/dist/app.module.js.map b/dist/app.module.js.map deleted file mode 100644 index 565e423..0000000 --- a/dist/app.module.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"app.module.js","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,qDAAiD;AACjD,+CAA2C;AAOpC,IAAM,SAAS,GAAf,MAAM,SAAS;CAAI,CAAA;AAAb,8BAAS;oBAAT,SAAS;IALrB,IAAA,eAAM,EAAC;QACJ,OAAO,EAAE,EAAE;QACX,WAAW,EAAE,CAAC,8BAAa,CAAC;QAC5B,SAAS,EAAE,CAAC,wBAAU,CAAC;KAC1B,CAAC;GACW,SAAS,CAAI"} \ No newline at end of file diff --git a/dist/app.service.d.ts b/dist/app.service.d.ts deleted file mode 100644 index 0496e79..0000000 --- a/dist/app.service.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -export declare class AppService { - getHello(): string; -} diff --git a/dist/app.service.js b/dist/app.service.js deleted file mode 100644 index 3bfb90b..0000000 --- a/dist/app.service.js +++ /dev/null @@ -1,20 +0,0 @@ -"use strict"; -var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.AppService = void 0; -const common_1 = require("@nestjs/common"); -let AppService = class AppService { - getHello() { - return 'Hello World!'; - } -}; -exports.AppService = AppService; -exports.AppService = AppService = __decorate([ - (0, common_1.Injectable)() -], AppService); -//# sourceMappingURL=app.service.js.map \ No newline at end of file diff --git a/dist/app.service.js.map b/dist/app.service.js.map deleted file mode 100644 index c99d561..0000000 --- a/dist/app.service.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"app.service.js","sourceRoot":"","sources":["../src/app.service.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAA4C;AAGrC,IAAM,UAAU,GAAhB,MAAM,UAAU;IACnB,QAAQ;QACJ,OAAO,cAAc,CAAC;IAC1B,CAAC;CACJ,CAAA;AAJY,gCAAU;qBAAV,UAAU;IADtB,IAAA,mBAAU,GAAE;GACA,UAAU,CAItB"} \ No newline at end of file diff --git a/dist/main.d.ts b/dist/main.d.ts deleted file mode 100644 index cb0ff5c..0000000 --- a/dist/main.d.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/dist/main.js b/dist/main.js deleted file mode 100644 index 274552e..0000000 --- a/dist/main.js +++ /dev/null @@ -1,10 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const core_1 = require("@nestjs/core"); -const app_module_1 = require("./app.module"); -async function bootstrap() { - const app = await core_1.NestFactory.create(app_module_1.AppModule); - await app.listen(process.env.PORT ?? 3000); -} -bootstrap(); -//# sourceMappingURL=main.js.map \ No newline at end of file diff --git a/dist/main.js.map b/dist/main.js.map deleted file mode 100644 index f7a5f41..0000000 --- a/dist/main.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";;AAAA,uCAA2C;AAC3C,6CAAyC;AAEzC,KAAK,UAAU,SAAS;IACpB,MAAM,GAAG,GAAG,MAAM,kBAAW,CAAC,MAAM,CAAC,sBAAS,CAAC,CAAC;IAChD,MAAM,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC;AAC/C,CAAC;AACD,SAAS,EAAE,CAAC"} \ No newline at end of file From d39b8eb2e8b29e87907632a159a30f9d885b16c7 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Fri, 8 Nov 2024 22:44:32 +0700 Subject: [PATCH 004/155] chore: Add auth guard and enums for roles and environment --- .env.example | 12 + .gitignore | 81 +- package.json | 15 +- pnpm-lock.yaml | 6610 +++++++++++++++++ src/app.module.ts | 33 +- src/auth/auth.controller.ts | 26 + src/auth/auth.guard.ts | 0 src/auth/auth.module.ts | 13 + src/auth/auth.service.ts | 61 + src/auth/dtos/auth-response.dto.ts | 7 + src/auth/dtos/jwt-payload.dto.ts | 6 + src/auth/dtos/login.dto.ts | 9 + src/auth/dtos/register.dto.ts | 3 + src/database/database.module.ts | 8 + src/database/database.provoders.ts | 11 + src/main.ts | 25 +- src/shared/configs/database.config.ts | 17 + src/shared/configs/dotenv.config.ts | 24 + .../constants/global-config.constant.ts | 15 + src/shared/enums/environment.enum.ts | 4 + src/shared/enums/roles.enum.ts | 5 + src/user/dtos/create-user.dto.ts | 16 + src/user/dtos/update-user.dto.ts | 11 + src/user/dtos/user-response.dto.ts | 18 + src/user/user.controller.ts | 9 + src/user/user.entity.ts | 49 + src/user/user.module.ts | 18 + src/user/user.providers.ts | 8 + src/user/user.service.ts | 37 + test/app.e2e-spec.ts | 24 + test/jest-e2e.json | 9 + 31 files changed, 7133 insertions(+), 51 deletions(-) create mode 100644 .env.example create mode 100644 pnpm-lock.yaml create mode 100644 src/auth/auth.controller.ts create mode 100644 src/auth/auth.guard.ts create mode 100644 src/auth/auth.module.ts create mode 100644 src/auth/auth.service.ts create mode 100644 src/auth/dtos/auth-response.dto.ts create mode 100644 src/auth/dtos/jwt-payload.dto.ts create mode 100644 src/auth/dtos/login.dto.ts create mode 100644 src/auth/dtos/register.dto.ts create mode 100644 src/database/database.module.ts create mode 100644 src/database/database.provoders.ts create mode 100644 src/shared/configs/database.config.ts create mode 100644 src/shared/configs/dotenv.config.ts create mode 100644 src/shared/constants/global-config.constant.ts create mode 100644 src/shared/enums/environment.enum.ts create mode 100644 src/shared/enums/roles.enum.ts create mode 100644 src/user/dtos/create-user.dto.ts create mode 100644 src/user/dtos/update-user.dto.ts create mode 100644 src/user/dtos/user-response.dto.ts create mode 100644 src/user/user.controller.ts create mode 100644 src/user/user.entity.ts create mode 100644 src/user/user.module.ts create mode 100644 src/user/user.providers.ts create mode 100644 src/user/user.service.ts create mode 100644 test/app.e2e-spec.ts create mode 100644 test/jest-e2e.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3bdfe5b --- /dev/null +++ b/.env.example @@ -0,0 +1,12 @@ +PORT=8080 +NODE_ENV=development +DB_HOST=localhost +DB_PORT=5432 +DB_USERNAME=postgres +DB_PASSWORD=qwerty +DB_DATABASE=edusaig +CORS_ALLOW_ORIGIN=http://localhost:5173 +JWT_ACCESS_SECRET=+W5LRxRFUk8MKSHMeRYevRg +JWT_REFRESH_SECRET=2Z7bB8GizN15kaHzB+H8Tg +JWT_ACCESS_EXPIRATION=1d +JWT_REFRESH_EXPIRATION=7d \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8071ebf..43ad4b2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,53 +1,40 @@ -packages/*/package-lock.json - -# dependencies -node_modules/ - -# IDE -/.idea -/.awcache -/.vscode -/.devcontainer -/.classpath -/.project -/.settings -*.code-workspace +# compiled output /dist - -# Vim -[._]*.s[a-v][a-z] -[._]*.sw[a-p] -[._]s[a-rt-v][a-z] -[._]ss[a-gi-z] -[._]sw[a-p] - -# bundle -packages/**/*.d.ts -packages/**/*.js - -# misc +/node_modules + +# Logs +logs +*.log +npm-debug.log* +pnpm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# OS .DS_Store -lerna-debug.log -npm-debug.log -yarn-error.log -/**/npm-debug.log -/packages/**/.npmignore -/packages/**/LICENSE -*.tsbuildinfo -# example -/quick-start -/example_dist -/example - -# tests -/test -/benchmarks/memory +# Tests /coverage /.nyc_output -/packages/graphql -/benchmarks/memory -build/config\.gypi -.npmrc -pnpm-lock.yaml \ No newline at end of file +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +.data +/files +.env +/ormconfig.json \ No newline at end of file diff --git a/package.json b/package.json index 286bdc3..8890557 100644 --- a/package.json +++ b/package.json @@ -20,11 +20,24 @@ "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { + "@nestjs/class-validator": "0.13.1", "@nestjs/common": "^10.0.0", + "@nestjs/config": "^3.3.0", "@nestjs/core": "^10.0.0", + "@nestjs/jwt": "^10.2.0", + "@nestjs/mapped-types": "^2.0.6", "@nestjs/platform-express": "^10.0.0", + "@nestjs/typeorm": "^10.0.2", + "argon2": "^0.41.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", + "joi": "^17.13.3", + "mysql2": "^3.11.4", + "pg": "^8.13.1", "reflect-metadata": "^0.2.0", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "typeorm": "^0.3.20", + "typeorm-extension": "^3.6.3" }, "devDependencies": { "@nestjs/cli": "^10.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..f9417da --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,6610 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@nestjs/class-validator': + specifier: 0.13.1 + version: 0.13.1 + '@nestjs/common': + specifier: ^10.0.0 + version: 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/config': + specifier: ^3.3.0 + version: 3.3.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(rxjs@7.8.1) + '@nestjs/core': + specifier: ^10.0.0 + version: 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/jwt': + specifier: ^10.2.0 + version: 10.2.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)) + '@nestjs/mapped-types': + specifier: ^2.0.6 + version: 2.0.6(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) + '@nestjs/platform-express': + specifier: ^10.0.0 + version: 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7) + '@nestjs/typeorm': + specifier: ^10.0.2 + version: 10.0.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3))) + argon2: + specifier: ^0.41.1 + version: 0.41.1 + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.1 + version: 0.14.1 + joi: + specifier: ^17.13.3 + version: 17.13.3 + mysql2: + specifier: ^3.11.4 + version: 3.11.4 + pg: + specifier: ^8.13.1 + version: 8.13.1 + reflect-metadata: + specifier: ^0.2.0 + version: 0.2.2 + rxjs: + specifier: ^7.8.1 + version: 7.8.1 + typeorm: + specifier: ^0.3.20 + version: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + typeorm-extension: + specifier: ^3.6.3 + version: 3.6.3(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3))) + devDependencies: + '@nestjs/cli': + specifier: ^10.0.0 + version: 10.4.7 + '@nestjs/schematics': + specifier: ^10.0.0 + version: 10.2.3(chokidar@3.6.0)(typescript@5.6.3) + '@nestjs/testing': + specifier: ^10.0.0 + version: 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@nestjs/platform-express@10.4.7) + '@types/express': + specifier: ^5.0.0 + version: 5.0.0 + '@types/jest': + specifier: ^29.5.2 + version: 29.5.14 + '@types/node': + specifier: ^20.3.1 + version: 20.17.6 + '@types/supertest': + specifier: ^6.0.0 + version: 6.0.2 + '@typescript-eslint/eslint-plugin': + specifier: ^8.0.0 + version: 8.13.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/parser': + specifier: ^8.0.0 + version: 8.13.0(eslint@8.57.1)(typescript@5.6.3) + eslint: + specifier: ^8.0.0 + version: 8.57.1 + eslint-config-prettier: + specifier: ^9.0.0 + version: 9.1.0(eslint@8.57.1) + eslint-plugin-prettier: + specifier: ^5.0.0 + version: 5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.3.3) + jest: + specifier: ^29.5.0 + version: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + prettier: + specifier: ^3.0.0 + version: 3.3.3 + source-map-support: + specifier: ^0.5.21 + version: 0.5.21 + supertest: + specifier: ^7.0.0 + version: 7.0.0 + ts-jest: + specifier: ^29.1.0 + version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)))(typescript@5.6.3) + ts-loader: + specifier: ^9.4.3 + version: 9.5.1(typescript@5.6.3)(webpack@5.96.1) + ts-node: + specifier: ^10.9.1 + version: 10.9.2(@types/node@20.17.6)(typescript@5.6.3) + tsconfig-paths: + specifier: ^4.2.0 + version: 4.2.0 + typescript: + specifier: ^5.1.3 + version: 5.6.3 + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@angular-devkit/core@17.3.11': + resolution: {integrity: sha512-vTNDYNsLIWpYk2I969LMQFH29GTsLzxNk/0cLw5q56ARF0v5sIWfHYwGTS88jdDqIpuuettcSczbxeA7EuAmqQ==} + engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^3.5.2 + peerDependenciesMeta: + chokidar: + optional: true + + '@angular-devkit/schematics-cli@17.3.11': + resolution: {integrity: sha512-kcOMqp+PHAKkqRad7Zd7PbpqJ0LqLaNZdY1+k66lLWmkEBozgq8v4ASn/puPWf9Bo0HpCiK+EzLf0VHE8Z/y6Q==} + engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + hasBin: true + + '@angular-devkit/schematics@17.3.11': + resolution: {integrity: sha512-I5wviiIqiFwar9Pdk30Lujk8FczEEc18i22A5c6Z9lbmhPQdTroDnEQdsfXjy404wPe8H62s0I15o4pmMGfTYQ==} + engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.26.2': + resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.26.0': + resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.26.2': + resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.25.9': + resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.25.9': + resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.26.0': + resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.26.2': + resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.26.0': + resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.25.9': + resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.25.9': + resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.25.9': + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.25.9': + resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.26.0': + resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@eslint-community/eslint-utils@4.4.1': + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@faker-js/faker@8.4.1': + resolution: {integrity: sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'} + + '@hapi/hoek@9.3.0': + resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + + '@hapi/topo@5.1.0': + resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jest/console@29.7.0': + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/core@29.7.0': + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect@29.7.0': + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/fake-timers@29.7.0': + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/globals@29.7.0': + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/reporters@29.7.0': + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/source-map@29.6.3': + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-result@29.7.0': + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-sequencer@29.7.0': + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@29.7.0': + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.6': + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@ljharb/through@2.3.13': + resolution: {integrity: sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==} + engines: {node: '>= 0.4'} + + '@lukeed/csprng@1.1.0': + resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} + engines: {node: '>=8'} + + '@nestjs/class-validator@0.13.1': + resolution: {integrity: sha512-Wwpg9KuGnkWOFleBPEFdHQKwqZsF/PFWQaeSaXwatkofgqD7N6AV5J30xIVgScDP+IcE+MdPGZJ38vHQ24KxPQ==} + + '@nestjs/cli@10.4.7': + resolution: {integrity: sha512-4wJTtBJsbvjLIzXl+Qj6DYHv4J7abotuXyk7bes5erL79y+KBT61LulL56SqilzmNnHOAVbXcSXOn9S2aWUn6A==} + engines: {node: '>= 16.14'} + hasBin: true + peerDependencies: + '@swc/cli': ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 + '@swc/core': ^1.3.62 + peerDependenciesMeta: + '@swc/cli': + optional: true + '@swc/core': + optional: true + + '@nestjs/common@10.4.7': + resolution: {integrity: sha512-gIOpjD3Mx8gfYGxYm/RHPcJzqdknNNFCyY+AxzBT3gc5Xvvik1Dn5OxaMGw5EbVfhZgJKVP0n83giUOAlZQe7w==} + peerDependencies: + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + + '@nestjs/config@3.3.0': + resolution: {integrity: sha512-pdGTp8m9d0ZCrjTpjkUbZx6gyf2IKf+7zlkrPNMsJzYZ4bFRRTpXrnj+556/5uiI6AfL5mMrJc2u7dB6bvM+VA==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + rxjs: ^7.1.0 + + '@nestjs/core@10.4.7': + resolution: {integrity: sha512-AIpQzW/vGGqSLkKvll1R7uaSNv99AxZI2EFyVJPNGDgFsfXaohfV1Ukl6f+s75Km+6Fj/7aNl80EqzNWQCS8Ig==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/microservices': ^10.0.0 + '@nestjs/platform-express': ^10.0.0 + '@nestjs/websockets': ^10.0.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + rxjs: ^7.1.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true + '@nestjs/websockets': + optional: true + + '@nestjs/jwt@10.2.0': + resolution: {integrity: sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + + '@nestjs/mapped-types@2.0.6': + resolution: {integrity: sha512-84ze+CPfp1OWdpRi1/lOu59hOhTz38eVzJvRKrg9ykRFwDz+XleKfMsG0gUqNZYFa6v53XYzeD+xItt8uDW7NQ==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + class-transformer: ^0.4.0 || ^0.5.0 + class-validator: ^0.13.0 || ^0.14.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true + + '@nestjs/platform-express@10.4.7': + resolution: {integrity: sha512-q6XDOxZPTZ9cxALcVuKUlRBk+cVEv6dW2S8p2yVre22kpEQxq53/OI8EseDvzObGb6hepZ8+yBY04qoYqSlXNQ==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/core': ^10.0.0 + + '@nestjs/schematics@10.2.3': + resolution: {integrity: sha512-4e8gxaCk7DhBxVUly2PjYL4xC2ifDFexCqq1/u4TtivLGXotVk0wHdYuPYe1tHTHuR1lsOkRbfOCpkdTnigLVg==} + peerDependencies: + typescript: '>=4.8.2' + + '@nestjs/testing@10.4.7': + resolution: {integrity: sha512-aS3sQ0v4g8cyHDzW3xJv1+8MiFAkxUNXmnau588IFFI/nBIo/kevLNHNPr85keYekkJ/lwNDW72h8UGg8BYd9w==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/core': ^10.0.0 + '@nestjs/microservices': ^10.0.0 + '@nestjs/platform-express': ^10.0.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true + + '@nestjs/typeorm@10.0.2': + resolution: {integrity: sha512-H738bJyydK4SQkRCTeh1aFBxoO1E9xdL/HaLGThwrqN95os5mEyAtK7BLADOS+vldP4jDZ2VQPLj4epWwRqCeQ==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + reflect-metadata: ^0.1.13 || ^0.2.0 + rxjs: ^7.2.0 + typeorm: ^0.3.0 + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@nuxtjs/opencollective@0.3.2': + resolution: {integrity: sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + + '@phc/format@1.0.0': + resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==} + engines: {node: '>=10'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@pkgr/core@0.1.1': + resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@sideway/address@4.1.5': + resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} + + '@sideway/formula@3.0.1': + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + + '@sideway/pinpoint@2.0.0': + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + + '@sqltools/formatter@1.2.5': + resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} + + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.8': + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.6': + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + + '@types/body-parser@1.19.5': + resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/cookiejar@2.1.5': + resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} + + '@types/eslint-scope@3.7.7': + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} + + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + + '@types/express-serve-static-core@5.0.1': + resolution: {integrity: sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==} + + '@types/express@5.0.0': + resolution: {integrity: sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==} + + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + + '@types/http-errors@2.0.4': + resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@29.5.14': + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/jsonwebtoken@9.0.5': + resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==} + + '@types/methods@1.1.4': + resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/node@20.17.6': + resolution: {integrity: sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==} + + '@types/qs@6.9.17': + resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/send@0.17.4': + resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} + + '@types/serve-static@1.15.7': + resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/superagent@8.1.9': + resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==} + + '@types/supertest@6.0.2': + resolution: {integrity: sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==} + + '@types/validator@13.12.2': + resolution: {integrity: sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.33': + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + + '@typescript-eslint/eslint-plugin@8.13.0': + resolution: {integrity: sha512-nQtBLiZYMUPkclSeC3id+x4uVd1SGtHuElTxL++SfP47jR0zfkZBJHc+gL4qPsgTuypz0k8Y2GheaDYn6Gy3rg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@8.13.0': + resolution: {integrity: sha512-w0xp+xGg8u/nONcGw1UXAr6cjCPU1w0XVyBs6Zqaj5eLmxkKQAByTdV/uGgNN5tVvN/kKpoQlP2cL7R+ajZZIQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@8.13.0': + resolution: {integrity: sha512-XsGWww0odcUT0gJoBZ1DeulY1+jkaHUciUq4jKNv4cpInbvvrtDoyBH9rE/n2V29wQJPk8iCH1wipra9BhmiMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/type-utils@8.13.0': + resolution: {integrity: sha512-Rqnn6xXTR316fP4D2pohZenJnp+NwQ1mo7/JM+J1LWZENSLkJI8ID8QNtlvFeb0HnFSK94D6q0cnMX6SbE5/vA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@8.13.0': + resolution: {integrity: sha512-4cyFErJetFLckcThRUFdReWJjVsPCqyBlJTi6IDEpc1GWCIIZRFxVppjWLIMcQhNGhdWJJRYFHpHoDWvMlDzng==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.13.0': + resolution: {integrity: sha512-v7SCIGmVsRK2Cy/LTLGN22uea6SaUIlpBcO/gnMGT/7zPtxp90bphcGf4fyrCQl3ZtiBKqVTG32hb668oIYy1g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@8.13.0': + resolution: {integrity: sha512-A1EeYOND6Uv250nybnLZapeXpYMl8tkzYUxqmoKAWnI4sei3ihf2XdZVd+vVOmHGcp3t+P7yRrNsyyiXTvShFQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + + '@typescript-eslint/visitor-keys@8.13.0': + resolution: {integrity: sha512-7N/+lztJqH4Mrf0lb10R/CbI1EaAMMGyF5y0oJvFoAhafwgiRA7TXyd8TFn8FC8k5y2dTsYogg238qavRGNnlw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@ungap/structured-clone@1.2.0': + resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + + '@webassemblyjs/ast@1.14.1': + resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} + + '@webassemblyjs/floating-point-hex-parser@1.13.2': + resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + + '@webassemblyjs/helper-api-error@1.13.2': + resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + + '@webassemblyjs/helper-buffer@1.14.1': + resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + + '@webassemblyjs/helper-numbers@1.13.2': + resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': + resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + + '@webassemblyjs/helper-wasm-section@1.14.1': + resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} + + '@webassemblyjs/ieee754@1.13.2': + resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} + + '@webassemblyjs/leb128@1.13.2': + resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} + + '@webassemblyjs/utf8@1.13.2': + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + + '@webassemblyjs/wasm-edit@1.14.1': + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} + + '@webassemblyjs/wasm-gen@1.14.1': + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} + + '@webassemblyjs/wasm-opt@1.14.1': + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} + + '@webassemblyjs/wasm-parser@1.14.1': + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} + + '@webassemblyjs/wast-printer@1.14.1': + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + + '@xtuc/ieee754@1.2.0': + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + + '@xtuc/long@4.2.2': + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-keywords@3.5.2: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + app-root-path@3.1.0: + resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} + engines: {node: '>= 6.0.0'} + + append-field@1.0.0: + resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + argon2@0.41.1: + resolution: {integrity: sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==} + engines: {node: '>=16.17.0'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + array-timsort@1.0.3: + resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + aws-ssl-profiles@1.1.2: + resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} + engines: {node: '>= 6.0.0'} + + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + + babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + babel-preset-current-node-syntax@1.1.0: + resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} + peerDependencies: + '@babel/core': ^7.0.0 + + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.24.2: + resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001679: + resolution: {integrity: sha512-j2YqID/YwpLnKzCmBOS4tlZdWprXm3ZmQLBH9ZBXFOhoxLA46fwyBvx6toCBWBmnuwUY/qB3kEU6gFx8qgCroA==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chrome-trace-event@1.0.4: + resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} + engines: {node: '>=6.0'} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + cjs-module-lexer@1.4.1: + resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} + + class-transformer@0.5.1: + resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} + + class-validator@0.14.1: + resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-highlight@2.1.11: + resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} + + cli-width@3.0.0: + resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} + engines: {node: '>= 10'} + + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + comment-json@4.2.5: + resolution: {integrity: sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==} + engines: {node: '>= 6'} + + component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + concat-stream@1.6.2: + resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} + engines: {'0': node >= 0.8} + + consola@2.15.3: + resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} + + consola@3.2.3: + resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} + engines: {node: ^14.18.0 || >=16.10.0} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + engines: {node: '>= 0.6'} + + cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + create-jest@29.7.0: + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cross-spawn@7.0.5: + resolution: {integrity: sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==} + engines: {node: '>= 8'} + + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destr@2.0.3: + resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dotenv-expand@10.0.0: + resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} + engines: {node: '>=12'} + + dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ebec@1.1.1: + resolution: {integrity: sha512-JZ1vcvPQtR+8LGbZmbjG21IxLQq/v47iheJqn2F6yB2CgnGfn8ZVg3myHrf3buIZS8UCwQK0jOSIb3oHX7aH8g==} + + ebec@2.3.0: + resolution: {integrity: sha512-bt+0tSL7223VU3PSVi0vtNLZ8pO1AfWolcPPMk2a/a5H+o/ZU9ky0n3A0zhrR4qzJTN61uPsGIO4ShhOukdzxA==} + + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} + engines: {node: '>=0.10.0'} + hasBin: true + + electron-to-chromium@1.5.55: + resolution: {integrity: sha512-6maZ2ASDOTBtjt9FhqYPRnbvKU5tjG0IN9SztUOWYw2AzNDNpKJYLJmlK0/En4Hs/aiWnB+JZ+gW19PIGszgKg==} + + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + enhanced-resolve@5.17.1: + resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} + engines: {node: '>=10.13.0'} + + envix@1.5.0: + resolution: {integrity: sha512-IOxTKT+tffjxgvX2O5nq6enbkv6kBQ/QdMy18bZWo0P0rKPvsRp2/EypIPwTvJfnmk3VdOlq/KcRSZCswefM/w==} + engines: {node: '>=18.0.0'} + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.5.4: + resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@9.1.0: + resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-prettier@5.2.1: + resolution: {integrity: sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '*' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + express@4.21.1: + resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==} + engines: {node: '>= 0.10.0'} + + external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + filelist@1.0.4: + resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@1.3.1: + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} + engines: {node: '>= 0.8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + + flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} + + fork-ts-checker-webpack-plugin@9.0.2: + resolution: {integrity: sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==} + engines: {node: '>=12.13.0', yarn: '>=1.0.0'} + peerDependencies: + typescript: '>3.6.0' + webpack: ^5.11.0 + + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + engines: {node: '>= 6'} + + formidable@3.5.2: + resolution: {integrity: sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-monkey@1.0.6: + resolution: {integrity: sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + generate-function@2.3.1: + resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + + glob@10.4.2: + resolution: {integrity: sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==} + engines: {node: '>=16 || 14 >=14.18'} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-own-prop@2.0.0: + resolution: {integrity: sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} + + has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hexoid@2.0.0: + resolution: {integrity: sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==} + engines: {node: '>=8'} + + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + inquirer@8.2.6: + resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} + engines: {node: '>=12.0.0'} + + inquirer@9.2.15: + resolution: {integrity: sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==} + engines: {node: '>=18'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-property@1.0.2: + resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} + + iterare@1.2.1: + resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} + engines: {node: '>=6'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jake@10.9.2: + resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==} + engines: {node: '>=10'} + hasBin: true + + jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-cli@29.7.0: + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@29.7.0: + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest@29.7.0: + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jiti@2.4.0: + resolution: {integrity: sha512-H5UpaUI+aHOqZXlYOaFP/8AzKsg+guWu+Pr3Y8i7+Y3zr1aXAvCvTAQ1RxSc6oVD8R8c7brgNtTVP91E7upH/g==} + hasBin: true + + joi@17.13.3: + resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@3.2.1: + resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + + jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + libphonenumber-js@1.11.13: + resolution: {integrity: sha512-LIJmXxgs7o1njVZPcX5fkbtcFgDnXXPvJQQBH5Ho/8+r6BFlJaEbJ+bAiaUGaChWUhFtvawwdmXIOz4wZBANCg==} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + loader-runner@4.3.0: + resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} + engines: {node: '>=6.11.5'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + locter@2.1.5: + resolution: {integrity: sha512-eI57PuVxigQ0GBscGIIFGPB467E5zKODHD3XGuknzLvf7HdnvRw3GdZVGj1J8XKsKOYovZQesX/oOdTwbdjwuQ==} + + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + long@5.2.3: + resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} + + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + + lru.min@1.1.1: + resolution: {integrity: sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==} + engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} + + magic-string@0.30.8: + resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} + engines: {node: '>=12'} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + memfs@3.5.3: + resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} + engines: {node: '>= 4.0.0'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true + + mkdirp@2.1.6: + resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} + engines: {node: '>=10'} + hasBin: true + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + multer@1.4.4-lts.1: + resolution: {integrity: sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==} + engines: {node: '>= 6.0.0'} + + mute-stream@0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + + mute-stream@1.0.0: + resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + mysql2@3.11.4: + resolution: {integrity: sha512-Z2o3tY4Z8EvSRDwknaC40MdZ3+m0sKbpnXrShQLdxPrAvcNli7jLrD2Zd2IzsRMw4eK9Yle500FDmlkIqp+krg==} + engines: {node: '>= 8.0'} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + named-placeholders@1.1.3: + resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} + engines: {node: '>=12.0.0'} + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + + node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + + node-addon-api@8.2.2: + resolution: {integrity: sha512-9emqXAKhVoNrQ792nLI/wpzPpJ/bj/YXxW0CvAau1+RdGBcCRF1Dmz7719zgVsQNrzHl9Tzn3ImZ4qWFarWL0A==} + engines: {node: ^18 || ^20 || >= 21} + + node-emoji@1.11.0: + resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-gyp-build@4.8.2: + resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==} + hasBin: true + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.18: + resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.2: + resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + engines: {node: '>= 0.4'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} + + parse5@5.1.1: + resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} + + parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + pascal-case@3.1.2: + resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-to-regexp@0.1.10: + resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} + + path-to-regexp@3.3.0: + resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pg-cloudflare@1.1.1: + resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} + + pg-connection-string@2.7.0: + resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} + + pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + pg-pool@3.7.0: + resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==} + peerDependencies: + pg: '>=8.0' + + pg-protocol@1.7.0: + resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} + + pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + + pg@8.13.1: + resolution: {integrity: sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + + pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.1: + resolution: {integrity: sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==} + engines: {node: '>=12'} + + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + + postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + + postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier@3.3.3: + resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + rapiq@0.9.0: + resolution: {integrity: sha512-k4oT4RarFBrlLMJ49xUTeQpa/us0uU4I70D/UEnK3FWQ4GENzei01rEQAmvPKAIzACo4NMW+YcYJ7EVfSa7EFg==} + + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + + repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve.exports@2.0.2: + resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} + engines: {node: '>=10'} + + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + run-async@2.4.1: + resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} + engines: {node: '>=0.12.0'} + + run-async@3.0.0: + resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} + engines: {node: '>=0.12.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + schema-utils@3.3.0: + resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} + engines: {node: '>= 10.13.0'} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true + + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: '>= 0.8.0'} + + seq-queue@0.0.5: + resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} + + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} + engines: {node: '>= 0.8.0'} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + sha.js@2.4.11: + resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} + hasBin: true + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + smob@1.5.0: + resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} + + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + sqlstring@2.3.3: + resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} + engines: {node: '>= 0.6'} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + superagent@9.0.2: + resolution: {integrity: sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==} + engines: {node: '>=14.18.0'} + + supertest@7.0.0: + resolution: {integrity: sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==} + engines: {node: '>=14.18.0'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + symbol-observable@4.0.0: + resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} + engines: {node: '>=0.10'} + + synckit@0.9.2: + resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==} + engines: {node: ^14.18.0 || >=16.0.0} + + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + + terser-webpack-plugin@5.3.10: + resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + + terser@5.36.0: + resolution: {integrity: sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==} + engines: {node: '>=10'} + hasBin: true + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-api-utils@1.4.0: + resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-jest@29.2.5: + resolution: {integrity: sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/transform': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + + ts-loader@9.5.1: + resolution: {integrity: sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==} + engines: {node: '>=12.0.0'} + peerDependencies: + typescript: '*' + webpack: ^5.0.0 + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tsconfig-paths-webpack-plugin@4.1.0: + resolution: {integrity: sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==} + engines: {node: '>=10.13.0'} + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + + tslib@2.7.0: + resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + + typeorm-extension@3.6.3: + resolution: {integrity: sha512-AE+8KqBphlBdVz5JS77o6LZzzi+b+YFFt8So4Qu/KRo/iynAwekrx98Oxuu3FAYNm6DUKDcubOBMZsJeiRvHkA==} + engines: {node: '>=14.0.0'} + hasBin: true + peerDependencies: + typeorm: ~0.3.0 + + typeorm@0.3.20: + resolution: {integrity: sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q==} + engines: {node: '>=16.13.0'} + hasBin: true + peerDependencies: + '@google-cloud/spanner': ^5.18.0 + '@sap/hana-client': ^2.12.25 + better-sqlite3: ^7.1.2 || ^8.0.0 || ^9.0.0 + hdb-pool: ^0.1.6 + ioredis: ^5.0.4 + mongodb: ^5.8.0 + mssql: ^9.1.1 || ^10.0.1 + mysql2: ^2.2.5 || ^3.0.1 + oracledb: ^6.3.0 + pg: ^8.5.1 + pg-native: ^3.0.0 + pg-query-stream: ^4.0.0 + redis: ^3.1.1 || ^4.0.0 + sql.js: ^1.4.0 + sqlite3: ^5.0.3 + ts-node: ^10.7.0 + typeorm-aurora-data-api-driver: ^2.0.0 + peerDependenciesMeta: + '@google-cloud/spanner': + optional: true + '@sap/hana-client': + optional: true + better-sqlite3: + optional: true + hdb-pool: + optional: true + ioredis: + optional: true + mongodb: + optional: true + mssql: + optional: true + mysql2: + optional: true + oracledb: + optional: true + pg: + optional: true + pg-native: + optional: true + pg-query-stream: + optional: true + redis: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + ts-node: + optional: true + typeorm-aurora-data-api-driver: + optional: true + + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} + engines: {node: '>=14.17'} + hasBin: true + + uid@2.0.2: + resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} + engines: {node: '>=8'} + + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.1.1: + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + validator@13.12.0: + resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} + engines: {node: '>= 0.10'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + watchpack@2.4.2: + resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==} + engines: {node: '>=10.13.0'} + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + webpack-node-externals@3.0.0: + resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} + engines: {node: '>=6'} + + webpack-sources@3.2.3: + resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} + engines: {node: '>=10.13.0'} + + webpack@5.96.1: + resolution: {integrity: sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@2.6.0: + resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==} + engines: {node: '>= 14'} + hasBin: true + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@angular-devkit/core@17.3.11(chokidar@3.6.0)': + dependencies: + ajv: 8.12.0 + ajv-formats: 2.1.1(ajv@8.12.0) + jsonc-parser: 3.2.1 + picomatch: 4.0.1 + rxjs: 7.8.1 + source-map: 0.7.4 + optionalDependencies: + chokidar: 3.6.0 + + '@angular-devkit/schematics-cli@17.3.11(chokidar@3.6.0)': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + ansi-colors: 4.1.3 + inquirer: 9.2.15 + symbol-observable: 4.0.0 + yargs-parser: 21.1.1 + transitivePeerDependencies: + - chokidar + + '@angular-devkit/schematics@17.3.11(chokidar@3.6.0)': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + jsonc-parser: 3.2.1 + magic-string: 0.30.8 + ora: 5.4.1 + rxjs: 7.8.1 + transitivePeerDependencies: + - chokidar + + '@babel/code-frame@7.26.2': + dependencies: + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.26.2': {} + + '@babel/core@7.26.0': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helpers': 7.26.0 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + convert-source-map: 2.0.0 + debug: 4.3.7 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.26.2': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.0.2 + + '@babel/helper-compilation-targets@7.25.9': + dependencies: + '@babel/compat-data': 7.26.2 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-module-imports@7.25.9': + dependencies: + '@babel/traverse': 7.25.9 + '@babel/types': 7.26.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.25.9': {} + + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helpers@7.26.0': + dependencies: + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + + '@babel/parser@7.26.2': + dependencies: + '@babel/types': 7.26.0 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/template@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + + '@babel/traverse@7.25.9': + dependencies: + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.2 + '@babel/parser': 7.26.2 + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + debug: 4.3.7 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.26.0': + dependencies: + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + + '@bcoe/v8-coverage@0.2.3': {} + + '@colors/colors@1.5.0': + optional: true + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.3.7 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@faker-js/faker@8.4.1': {} + + '@hapi/hoek@9.3.0': {} + + '@hapi/topo@5.1.0': + dependencies: + '@hapi/hoek': 9.3.0 + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.3.7 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jest/console@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.17.6 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + + '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3))': + dependencies: + '@jest/console': 29.7.0 + '@jest/reporters': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.17.6 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-resolve-dependencies: 29.7.0 + jest-runner: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + jest-watcher: 29.7.0 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.17.6 + jest-mock: 29.7.0 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/expect@29.7.0': + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 20.17.6 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + '@jest/globals@29.7.0': + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/types': 29.6.3 + jest-mock: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/reporters@29.7.0': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + '@types/node': 20.17.6 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.1.7 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + + '@jest/source-map@29.6.3': + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.2 + + '@jest/test-sequencer@29.7.0': + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + + '@jest/transform@29.7.0': + dependencies: + '@babel/core': 7.26.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.25 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.8 + pirates: 4.0.6 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 20.17.6 + '@types/yargs': 17.0.33 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/source-map@0.3.6': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@ljharb/through@2.3.13': + dependencies: + call-bind: 1.0.7 + + '@lukeed/csprng@1.1.0': {} + + '@nestjs/class-validator@0.13.1': + dependencies: + '@types/validator': 13.12.2 + libphonenumber-js: 1.11.13 + validator: 13.12.0 + + '@nestjs/cli@10.4.7': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics-cli': 17.3.11(chokidar@3.6.0) + '@nestjs/schematics': 10.2.3(chokidar@3.6.0)(typescript@5.6.3) + chalk: 4.1.2 + chokidar: 3.6.0 + cli-table3: 0.6.5 + commander: 4.1.1 + fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.6.3)(webpack@5.96.1) + glob: 10.4.2 + inquirer: 8.2.6 + node-emoji: 1.11.0 + ora: 5.4.1 + tree-kill: 1.2.2 + tsconfig-paths: 4.2.0 + tsconfig-paths-webpack-plugin: 4.1.0 + typescript: 5.6.3 + webpack: 5.96.1 + webpack-node-externals: 3.0.0 + transitivePeerDependencies: + - esbuild + - uglify-js + - webpack-cli + + '@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)': + dependencies: + iterare: 1.2.1 + reflect-metadata: 0.2.2 + rxjs: 7.8.1 + tslib: 2.7.0 + uid: 2.0.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.1 + + '@nestjs/config@3.3.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(rxjs@7.8.1)': + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + dotenv: 16.4.5 + dotenv-expand: 10.0.0 + lodash: 4.17.21 + rxjs: 7.8.1 + + '@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1)': + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nuxtjs/opencollective': 0.3.2 + fast-safe-stringify: 2.1.1 + iterare: 1.2.1 + path-to-regexp: 3.3.0 + reflect-metadata: 0.2.2 + rxjs: 7.8.1 + tslib: 2.7.0 + uid: 2.0.2 + optionalDependencies: + '@nestjs/platform-express': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7) + transitivePeerDependencies: + - encoding + + '@nestjs/jwt@10.2.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))': + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@types/jsonwebtoken': 9.0.5 + jsonwebtoken: 9.0.2 + + '@nestjs/mapped-types@2.0.6(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)': + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + reflect-metadata: 0.2.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.1 + + '@nestjs/platform-express@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)': + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + body-parser: 1.20.3 + cors: 2.8.5 + express: 4.21.1 + multer: 1.4.4-lts.1 + tslib: 2.7.0 + transitivePeerDependencies: + - supports-color + + '@nestjs/schematics@10.2.3(chokidar@3.6.0)(typescript@5.6.3)': + dependencies: + '@angular-devkit/core': 17.3.11(chokidar@3.6.0) + '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + comment-json: 4.2.5 + jsonc-parser: 3.3.1 + pluralize: 8.0.0 + typescript: 5.6.3 + transitivePeerDependencies: + - chokidar + + '@nestjs/testing@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@nestjs/platform-express@10.4.7)': + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + tslib: 2.7.0 + optionalDependencies: + '@nestjs/platform-express': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7) + + '@nestjs/typeorm@10.0.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)))': + dependencies: + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + reflect-metadata: 0.2.2 + rxjs: 7.8.1 + typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + uuid: 9.0.1 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.17.1 + + '@nuxtjs/opencollective@0.3.2': + dependencies: + chalk: 4.1.2 + consola: 2.15.3 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + '@phc/format@1.0.0': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.1.1': {} + + '@sideway/address@4.1.5': + dependencies: + '@hapi/hoek': 9.3.0 + + '@sideway/formula@3.0.1': {} + + '@sideway/pinpoint@2.0.0': {} + + '@sinclair/typebox@0.27.8': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@sqltools/formatter@1.2.5': {} + + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + '@types/babel__generator': 7.6.8 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.6 + + '@types/babel__generator@7.6.8': + dependencies: + '@babel/types': 7.26.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.26.2 + '@babel/types': 7.26.0 + + '@types/babel__traverse@7.20.6': + dependencies: + '@babel/types': 7.26.0 + + '@types/body-parser@1.19.5': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 20.17.6 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 20.17.6 + + '@types/cookiejar@2.1.5': {} + + '@types/eslint-scope@3.7.7': + dependencies: + '@types/eslint': 9.6.1 + '@types/estree': 1.0.6 + + '@types/eslint@9.6.1': + dependencies: + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 + + '@types/estree@1.0.6': {} + + '@types/express-serve-static-core@5.0.1': + dependencies: + '@types/node': 20.17.6 + '@types/qs': 6.9.17 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.4 + + '@types/express@5.0.0': + dependencies: + '@types/body-parser': 1.19.5 + '@types/express-serve-static-core': 5.0.1 + '@types/qs': 6.9.17 + '@types/serve-static': 1.15.7 + + '@types/graceful-fs@4.1.9': + dependencies: + '@types/node': 20.17.6 + + '@types/http-errors@2.0.4': {} + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@29.5.14': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + + '@types/json-schema@7.0.15': {} + + '@types/jsonwebtoken@9.0.5': + dependencies: + '@types/node': 20.17.6 + + '@types/methods@1.1.4': {} + + '@types/mime@1.3.5': {} + + '@types/node@20.17.6': + dependencies: + undici-types: 6.19.8 + + '@types/qs@6.9.17': {} + + '@types/range-parser@1.2.7': {} + + '@types/send@0.17.4': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 20.17.6 + + '@types/serve-static@1.15.7': + dependencies: + '@types/http-errors': 2.0.4 + '@types/node': 20.17.6 + '@types/send': 0.17.4 + + '@types/stack-utils@2.0.3': {} + + '@types/superagent@8.1.9': + dependencies: + '@types/cookiejar': 2.1.5 + '@types/methods': 1.1.4 + '@types/node': 20.17.6 + form-data: 4.0.1 + + '@types/supertest@6.0.2': + dependencies: + '@types/methods': 1.1.4 + '@types/superagent': 8.1.9 + + '@types/validator@13.12.2': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.33': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@typescript-eslint/eslint-plugin@8.13.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.13.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/scope-manager': 8.13.0 + '@typescript-eslint/type-utils': 8.13.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/utils': 8.13.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.13.0 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 1.4.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.13.0 + '@typescript-eslint/types': 8.13.0 + '@typescript-eslint/typescript-estree': 8.13.0(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.13.0 + debug: 4.3.7 + eslint: 8.57.1 + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.13.0': + dependencies: + '@typescript-eslint/types': 8.13.0 + '@typescript-eslint/visitor-keys': 8.13.0 + + '@typescript-eslint/type-utils@8.13.0(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.13.0(typescript@5.6.3) + '@typescript-eslint/utils': 8.13.0(eslint@8.57.1)(typescript@5.6.3) + debug: 4.3.7 + ts-api-utils: 1.4.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - eslint + - supports-color + + '@typescript-eslint/types@8.13.0': {} + + '@typescript-eslint/typescript-estree@8.13.0(typescript@5.6.3)': + dependencies: + '@typescript-eslint/types': 8.13.0 + '@typescript-eslint/visitor-keys': 8.13.0 + debug: 4.3.7 + fast-glob: 3.3.2 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.3 + ts-api-utils: 1.4.0(typescript@5.6.3) + optionalDependencies: + typescript: 5.6.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.13.0(eslint@8.57.1)(typescript@5.6.3)': + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@typescript-eslint/scope-manager': 8.13.0 + '@typescript-eslint/types': 8.13.0 + '@typescript-eslint/typescript-estree': 8.13.0(typescript@5.6.3) + eslint: 8.57.1 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@8.13.0': + dependencies: + '@typescript-eslint/types': 8.13.0 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.2.0': {} + + '@webassemblyjs/ast@1.14.1': + dependencies: + '@webassemblyjs/helper-numbers': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + + '@webassemblyjs/floating-point-hex-parser@1.13.2': {} + + '@webassemblyjs/helper-api-error@1.13.2': {} + + '@webassemblyjs/helper-buffer@1.14.1': {} + + '@webassemblyjs/helper-numbers@1.13.2': + dependencies: + '@webassemblyjs/floating-point-hex-parser': 1.13.2 + '@webassemblyjs/helper-api-error': 1.13.2 + '@xtuc/long': 4.2.2 + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} + + '@webassemblyjs/helper-wasm-section@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/wasm-gen': 1.14.1 + + '@webassemblyjs/ieee754@1.13.2': + dependencies: + '@xtuc/ieee754': 1.2.0 + + '@webassemblyjs/leb128@1.13.2': + dependencies: + '@xtuc/long': 4.2.2 + + '@webassemblyjs/utf8@1.13.2': {} + + '@webassemblyjs/wasm-edit@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/helper-wasm-section': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-opt': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + '@webassemblyjs/wast-printer': 1.14.1 + + '@webassemblyjs/wasm-gen@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wasm-opt@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + + '@webassemblyjs/wasm-parser@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-api-error': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wast-printer@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@xtuc/long': 4.2.2 + + '@xtuc/ieee754@1.2.0': {} + + '@xtuc/long@4.2.2': {} + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + acorn-jsx@5.3.2(acorn@8.14.0): + dependencies: + acorn: 8.14.0 + + acorn-walk@8.3.4: + dependencies: + acorn: 8.14.0 + + acorn@8.14.0: {} + + ajv-formats@2.1.1(ajv@8.12.0): + optionalDependencies: + ajv: 8.12.0 + + ajv-keywords@3.5.2(ajv@6.12.6): + dependencies: + ajv: 6.12.6 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.12.0: + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + + ansi-colors@4.1.3: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.1: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + app-root-path@3.1.0: {} + + append-field@1.0.0: {} + + arg@4.1.3: {} + + argon2@0.41.1: + dependencies: + '@phc/format': 1.0.0 + node-addon-api: 8.2.2 + node-gyp-build: 4.8.2 + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + array-flatten@1.1.1: {} + + array-timsort@1.0.3: {} + + asap@2.0.6: {} + + async@3.2.6: {} + + asynckit@0.4.0: {} + + aws-ssl-profiles@1.1.2: {} + + babel-jest@29.7.0(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.26.0) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@6.1.1: + dependencies: + '@babel/helper-plugin-utils': 7.25.9 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@29.6.3: + dependencies: + '@babel/template': 7.25.9 + '@babel/types': 7.26.0 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.20.6 + + babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.26.0) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.26.0) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.26.0) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.26.0) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.26.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.26.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.0) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.0) + + babel-preset-jest@29.6.3(@babel/core@7.26.0): + dependencies: + '@babel/core': 7.26.0 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.0) + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + binary-extensions@2.3.0: {} + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + body-parser@1.20.3: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.13.0 + raw-body: 2.5.2 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.24.2: + dependencies: + caniuse-lite: 1.0.30001679 + electron-to-chromium: 1.5.55 + node-releases: 2.0.18 + update-browserslist-db: 1.1.1(browserslist@4.24.2) + + bs-logger@0.2.6: + dependencies: + fast-json-stable-stringify: 2.1.0 + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-equal-constant-time@1.0.1: {} + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + bytes@3.1.2: {} + + call-bind@1.0.7: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + set-function-length: 1.2.2 + + callsites@3.1.0: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001679: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.3.0: {} + + char-regex@1.0.2: {} + + chardet@0.7.0: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chrome-trace-event@1.0.4: {} + + ci-info@3.9.0: {} + + cjs-module-lexer@1.4.1: {} + + class-transformer@0.5.1: {} + + class-validator@0.14.1: + dependencies: + '@types/validator': 13.12.2 + libphonenumber-js: 1.11.13 + validator: 13.12.0 + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-highlight@2.1.11: + dependencies: + chalk: 4.1.2 + highlight.js: 10.7.3 + mz: 2.7.0 + parse5: 5.1.1 + parse5-htmlparser2-tree-adapter: 6.0.1 + yargs: 16.2.0 + + cli-spinners@2.9.2: {} + + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + + cli-width@3.0.0: {} + + cli-width@4.1.0: {} + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone@1.0.4: {} + + co@4.6.0: {} + + collect-v8-coverage@1.0.2: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@2.20.3: {} + + commander@4.1.1: {} + + comment-json@4.2.5: + dependencies: + array-timsort: 1.0.3 + core-util-is: 1.0.3 + esprima: 4.0.1 + has-own-prop: 2.0.0 + repeat-string: 1.6.1 + + component-emitter@1.3.1: {} + + concat-map@0.0.1: {} + + concat-stream@1.6.2: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 2.3.8 + typedarray: 0.0.6 + + consola@2.15.3: {} + + consola@3.2.3: {} + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + convert-source-map@2.0.0: {} + + cookie-signature@1.0.6: {} + + cookie@0.7.1: {} + + cookiejar@2.1.4: {} + + core-util-is@1.0.3: {} + + cors@2.8.5: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cosmiconfig@8.3.6(typescript@5.6.3): + dependencies: + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.6.3 + + create-jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + create-require@1.1.1: {} + + cross-spawn@7.0.5: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + dayjs@1.11.13: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.3.7: + dependencies: + ms: 2.1.3 + + dedent@1.5.3: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.0 + es-errors: 1.3.0 + gopd: 1.0.1 + + delayed-stream@1.0.0: {} + + denque@2.1.0: {} + + depd@2.0.0: {} + + destr@2.0.3: {} + + destroy@1.2.0: {} + + detect-newline@3.1.0: {} + + dezalgo@1.0.4: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + + diff-sequences@29.6.3: {} + + diff@4.0.2: {} + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dotenv-expand@10.0.0: {} + + dotenv@16.4.5: {} + + eastasianwidth@0.2.0: {} + + ebec@1.1.1: + dependencies: + smob: 1.5.0 + + ebec@2.3.0: {} + + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + ee-first@1.1.1: {} + + ejs@3.1.10: + dependencies: + jake: 10.9.2 + + electron-to-chromium@1.5.55: {} + + emittery@0.13.1: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encodeurl@1.0.2: {} + + encodeurl@2.0.0: {} + + enhanced-resolve@5.17.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + + envix@1.5.0: + dependencies: + std-env: 3.7.0 + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.0: + dependencies: + get-intrinsic: 1.2.4 + + es-errors@1.3.0: {} + + es-module-lexer@1.5.4: {} + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@9.1.0(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + + eslint-plugin-prettier@5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.3.3): + dependencies: + eslint: 8.57.1 + prettier: 3.3.3 + prettier-linter-helpers: 1.0.0 + synckit: 0.9.2 + optionalDependencies: + '@types/eslint': 9.6.1 + eslint-config-prettier: 9.1.0(eslint@8.57.1) + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.1 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.2.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.5 + debug: 4.3.7 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 3.4.3 + + esprima@4.0.1: {} + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + events@3.3.0: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.5 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exit@0.1.2: {} + + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + + express@4.21.1: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.3 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.1 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.1 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.10 + proxy-addr: 2.0.7 + qs: 6.13.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.0 + serve-static: 1.16.2 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.2: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-safe-stringify@2.1.1: {} + + fastq@1.17.1: + dependencies: + reusify: 1.0.4 + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + figures@3.2.0: + dependencies: + escape-string-regexp: 1.0.5 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + filelist@1.0.4: + dependencies: + minimatch: 5.1.6 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@1.3.1: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.3.1 + keyv: 4.5.4 + rimraf: 3.0.2 + + flat@5.0.2: {} + + flatted@3.3.1: {} + + foreground-child@3.3.0: + dependencies: + cross-spawn: 7.0.5 + signal-exit: 4.1.0 + + fork-ts-checker-webpack-plugin@9.0.2(typescript@5.6.3)(webpack@5.96.1): + dependencies: + '@babel/code-frame': 7.26.2 + chalk: 4.1.2 + chokidar: 3.6.0 + cosmiconfig: 8.3.6(typescript@5.6.3) + deepmerge: 4.3.1 + fs-extra: 10.1.0 + memfs: 3.5.3 + minimatch: 3.1.2 + node-abort-controller: 3.1.1 + schema-utils: 3.3.0 + semver: 7.6.3 + tapable: 2.2.1 + typescript: 5.6.3 + webpack: 5.96.1 + + form-data@4.0.1: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + formidable@3.5.2: + dependencies: + dezalgo: 1.0.4 + hexoid: 2.0.0 + once: 1.4.0 + + forwarded@0.2.0: {} + + fresh@0.5.2: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs-monkey@1.0.6: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + generate-function@2.3.1: + dependencies: + is-property: 1.0.2 + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.2.4: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + has-proto: 1.0.3 + has-symbols: 1.0.3 + hasown: 2.0.2 + + get-package-type@0.1.0: {} + + get-stream@6.0.1: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob-to-regexp@0.4.1: {} + + glob@10.4.2: + dependencies: + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@11.12.0: {} + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + gopd@1.0.1: + dependencies: + get-intrinsic: 1.2.4 + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + has-flag@4.0.0: {} + + has-own-prop@2.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.0 + + has-proto@1.0.3: {} + + has-symbols@1.0.3: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hexoid@2.0.0: {} + + highlight.js@10.7.3: {} + + html-escaper@2.0.2: {} + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + human-signals@2.1.0: {} + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + import-fresh@3.3.0: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + imurmurhash@0.1.4: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + inquirer@8.2.6: + dependencies: + ansi-escapes: 4.3.2 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-width: 3.0.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 0.0.8 + ora: 5.4.1 + run-async: 2.4.1 + rxjs: 7.8.1 + string-width: 4.2.3 + strip-ansi: 6.0.1 + through: 2.3.8 + wrap-ansi: 6.2.0 + + inquirer@9.2.15: + dependencies: + '@ljharb/through': 2.3.13 + ansi-escapes: 4.3.2 + chalk: 5.3.0 + cli-cursor: 3.1.0 + cli-width: 4.1.0 + external-editor: 3.1.0 + figures: 3.2.0 + lodash: 4.17.21 + mute-stream: 1.0.0 + ora: 5.4.1 + run-async: 3.0.0 + rxjs: 7.8.1 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + ipaddr.js@1.9.1: {} + + is-arrayish@0.2.1: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-core-module@2.15.1: + dependencies: + hasown: 2.0.2 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-generator-fn@2.1.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-interactive@1.0.0: {} + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + is-property@1.0.2: {} + + is-stream@2.0.1: {} + + is-unicode-supported@0.1.0: {} + + isarray@1.0.0: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@5.2.1: + dependencies: + '@babel/core': 7.26.0 + '@babel/parser': 7.26.2 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.26.0 + '@babel/parser': 7.26.2 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@4.0.1: + dependencies: + debug: 4.3.7 + istanbul-lib-coverage: 3.2.2 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + iterare@1.2.1: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jake@10.9.2: + dependencies: + async: 3.2.6 + chalk: 4.1.2 + filelist: 1.0.4 + minimatch: 3.1.2 + + jest-changed-files@29.7.0: + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + + jest-circus@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/expect': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.17.6 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.5.3 + is-generator-fn: 2.1.0 + jest-each: 29.7.0 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-runtime: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + p-limit: 3.1.0 + pretty-format: 29.7.0 + pure-rand: 6.1.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jest-config@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): + dependencies: + '@babel/core': 7.26.0 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.0) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.17.6 + ts-node: 10.9.2(@types/node@20.17.6)(typescript@5.6.3) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-docblock@29.7.0: + dependencies: + detect-newline: 3.1.0 + + jest-each@29.7.0: + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + + jest-environment-node@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.17.6 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-haste-map@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 20.17.6 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-leak-detector@29.7.0: + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.26.2 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.17.6 + jest-util: 29.7.0 + + jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + optionalDependencies: + jest-resolve: 29.7.0 + + jest-regex-util@29.6.3: {} + + jest-resolve-dependencies@29.7.0: + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + jest-resolve@29.7.0: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.8 + resolve.exports: 2.0.2 + slash: 3.0.0 + + jest-runner@29.7.0: + dependencies: + '@jest/console': 29.7.0 + '@jest/environment': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.17.6 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + jest-docblock: 29.7.0 + jest-environment-node: 29.7.0 + jest-haste-map: 29.7.0 + jest-leak-detector: 29.7.0 + jest-message-util: 29.7.0 + jest-resolve: 29.7.0 + jest-runtime: 29.7.0 + jest-util: 29.7.0 + jest-watcher: 29.7.0 + jest-worker: 29.7.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + jest-runtime@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/globals': 29.7.0 + '@jest/source-map': 29.6.3 + '@jest/test-result': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.17.6 + chalk: 4.1.2 + cjs-module-lexer: 1.4.1 + collect-v8-coverage: 1.0.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-snapshot: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-snapshot@29.7.0: + dependencies: + '@babel/core': 7.26.0 + '@babel/generator': 7.26.2 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) + '@babel/types': 7.26.0 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.0) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + natural-compare: 1.4.0 + pretty-format: 29.7.0 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.17.6 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jest-watcher@29.7.0: + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.17.6 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 + + jest-worker@27.5.1: + dependencies: + '@types/node': 20.17.6 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest-worker@29.7.0: + dependencies: + '@types/node': 20.17.6 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): + dependencies: + '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jiti@2.4.0: {} + + joi@17.13.3: + dependencies: + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.5 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 + + js-tokens@4.0.0: {} + + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.0.2: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonc-parser@3.2.1: {} + + jsonc-parser@3.3.1: {} + + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsonwebtoken@9.0.2: + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.6.3 + + jwa@1.4.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@3.2.2: + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kleur@3.0.3: {} + + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + libphonenumber-js@1.11.13: {} + + lines-and-columns@1.2.4: {} + + loader-runner@4.3.0: {} + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + locter@2.1.5: + dependencies: + destr: 2.0.3 + ebec: 2.3.0 + fast-glob: 3.3.2 + flat: 5.0.2 + jiti: 2.4.0 + yaml: 2.6.0 + + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.memoize@4.1.2: {} + + lodash.merge@4.6.2: {} + + lodash.once@4.1.1: {} + + lodash@4.17.21: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + long@5.2.3: {} + + lower-case@2.0.2: + dependencies: + tslib: 2.8.1 + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lru-cache@7.18.3: {} + + lru.min@1.1.1: {} + + magic-string@0.30.8: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + + make-dir@4.0.0: + dependencies: + semver: 7.6.3 + + make-error@1.3.6: {} + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + media-typer@0.3.0: {} + + memfs@3.5.3: + dependencies: + fs-monkey: 1.0.6 + + merge-descriptors@1.0.3: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + methods@1.1.2: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + mime@2.6.0: {} + + mimic-fn@2.1.0: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.11 + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.1 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + mkdirp@0.5.6: + dependencies: + minimist: 1.2.8 + + mkdirp@2.1.6: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + multer@1.4.4-lts.1: + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + concat-stream: 1.6.2 + mkdirp: 0.5.6 + object-assign: 4.1.1 + type-is: 1.6.18 + xtend: 4.0.2 + + mute-stream@0.0.8: {} + + mute-stream@1.0.0: {} + + mysql2@3.11.4: + dependencies: + aws-ssl-profiles: 1.1.2 + denque: 2.1.0 + generate-function: 2.3.1 + iconv-lite: 0.6.3 + long: 5.2.3 + lru.min: 1.1.1 + named-placeholders: 1.1.3 + seq-queue: 0.0.5 + sqlstring: 2.3.3 + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + named-placeholders@1.1.3: + dependencies: + lru-cache: 7.18.3 + + natural-compare@1.4.0: {} + + negotiator@0.6.3: {} + + neo-async@2.6.2: {} + + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.8.1 + + node-abort-controller@3.1.1: {} + + node-addon-api@8.2.2: {} + + node-emoji@1.11.0: + dependencies: + lodash: 4.17.21 + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-gyp-build@4.8.2: {} + + node-int64@0.4.0: {} + + node-releases@2.0.18: {} + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + object-assign@4.1.1: {} + + object-inspect@1.13.2: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + ora@5.4.1: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + + os-tmpdir@1.0.2: {} + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.26.2 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse5-htmlparser2-tree-adapter@6.0.1: + dependencies: + parse5: 6.0.1 + + parse5@5.1.1: {} + + parse5@6.0.1: {} + + parseurl@1.3.3: {} + + pascal-case@3.1.2: + dependencies: + no-case: 3.0.4 + tslib: 2.8.1 + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-to-regexp@0.1.10: {} + + path-to-regexp@3.3.0: {} + + path-type@4.0.0: {} + + pg-cloudflare@1.1.1: + optional: true + + pg-connection-string@2.7.0: {} + + pg-int8@1.0.1: {} + + pg-pool@3.7.0(pg@8.13.1): + dependencies: + pg: 8.13.1 + + pg-protocol@1.7.0: {} + + pg-types@2.2.0: + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + pg@8.13.1: + dependencies: + pg-connection-string: 2.7.0 + pg-pool: 3.7.0(pg@8.13.1) + pg-protocol: 1.7.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.1.1 + + pgpass@1.0.5: + dependencies: + split2: 4.2.0 + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.1: {} + + pirates@4.0.6: {} + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + pluralize@8.0.0: {} + + postgres-array@2.0.0: {} + + postgres-bytea@1.0.0: {} + + postgres-date@1.0.7: {} + + postgres-interval@1.2.0: + dependencies: + xtend: 4.0.2 + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@3.3.3: {} + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + process-nextick-args@2.0.1: {} + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + punycode@2.3.1: {} + + pure-rand@6.1.0: {} + + qs@6.13.0: + dependencies: + side-channel: 1.0.6 + + queue-microtask@1.2.3: {} + + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + range-parser@1.2.1: {} + + rapiq@0.9.0: + dependencies: + ebec: 1.1.1 + smob: 1.5.0 + + raw-body@2.5.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + react-is@18.3.1: {} + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + reflect-metadata@0.2.2: {} + + repeat-string@1.6.1: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve.exports@2.0.2: {} + + resolve@1.22.8: + dependencies: + is-core-module: 2.15.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + reusify@1.0.4: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + run-async@2.4.1: {} + + run-async@3.0.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rxjs@7.8.1: + dependencies: + tslib: 2.8.1 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + schema-utils@3.3.0: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + ajv-keywords: 3.5.2(ajv@6.12.6) + + semver@6.3.1: {} + + semver@7.6.3: {} + + send@0.19.0: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + + seq-queue@0.0.5: {} + + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + + serve-static@1.16.2: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.0 + transitivePeerDependencies: + - supports-color + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.2.4 + gopd: 1.0.1 + has-property-descriptors: 1.0.2 + + setprototypeof@1.2.0: {} + + sha.js@2.4.11: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel@1.0.6: + dependencies: + call-bind: 1.0.7 + es-errors: 1.3.0 + get-intrinsic: 1.2.4 + object-inspect: 1.13.2 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + sisteransi@1.0.5: {} + + slash@3.0.0: {} + + smob@1.5.0: {} + + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + source-map@0.7.4: {} + + split2@4.2.0: {} + + sprintf-js@1.0.3: {} + + sqlstring@2.3.3: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + statuses@2.0.1: {} + + std-env@3.7.0: {} + + streamsearch@1.1.0: {} + + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-bom@3.0.0: {} + + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-json-comments@3.1.1: {} + + superagent@9.0.2: + dependencies: + component-emitter: 1.3.1 + cookiejar: 2.1.4 + debug: 4.3.7 + fast-safe-stringify: 2.1.1 + form-data: 4.0.1 + formidable: 3.5.2 + methods: 1.1.2 + mime: 2.6.0 + qs: 6.13.0 + transitivePeerDependencies: + - supports-color + + supertest@7.0.0: + dependencies: + methods: 1.1.2 + superagent: 9.0.2 + transitivePeerDependencies: + - supports-color + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + symbol-observable@4.0.0: {} + + synckit@0.9.2: + dependencies: + '@pkgr/core': 0.1.1 + tslib: 2.8.1 + + tapable@2.2.1: {} + + terser-webpack-plugin@5.3.10(webpack@5.96.1): + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + jest-worker: 27.5.1 + schema-utils: 3.3.0 + serialize-javascript: 6.0.2 + terser: 5.36.0 + webpack: 5.96.1 + + terser@5.36.0: + dependencies: + '@jridgewell/source-map': 0.3.6 + acorn: 8.14.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + + text-table@0.2.0: {} + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + through@2.3.8: {} + + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + + tmpl@1.0.5: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + tr46@0.0.3: {} + + tree-kill@1.2.2: {} + + ts-api-utils@1.4.0(typescript@5.6.3): + dependencies: + typescript: 5.6.3 + + ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)))(typescript@5.6.3): + dependencies: + bs-logger: 0.2.6 + ejs: 3.1.10 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.6.3 + typescript: 5.6.3 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.26.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.0) + + ts-loader@9.5.1(typescript@5.6.3)(webpack@5.96.1): + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.17.1 + micromatch: 4.0.8 + semver: 7.6.3 + source-map: 0.7.4 + typescript: 5.6.3 + webpack: 5.96.1 + + ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 20.17.6 + acorn: 8.14.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.6.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + tsconfig-paths-webpack-plugin@4.1.0: + dependencies: + chalk: 4.1.2 + enhanced-resolve: 5.17.1 + tsconfig-paths: 4.2.0 + + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.7.0: {} + + tslib@2.8.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.0.8: {} + + type-fest@0.20.2: {} + + type-fest@0.21.3: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typedarray@0.0.6: {} + + typeorm-extension@3.6.3(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3))): + dependencies: + '@faker-js/faker': 8.4.1 + consola: 3.2.3 + envix: 1.5.0 + locter: 2.1.5 + pascal-case: 3.1.2 + rapiq: 0.9.0 + reflect-metadata: 0.2.2 + smob: 1.5.0 + typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + yargs: 17.7.2 + + typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): + dependencies: + '@sqltools/formatter': 1.2.5 + app-root-path: 3.1.0 + buffer: 6.0.3 + chalk: 4.1.2 + cli-highlight: 2.1.11 + dayjs: 1.11.13 + debug: 4.3.7 + dotenv: 16.4.5 + glob: 10.4.2 + mkdirp: 2.1.6 + reflect-metadata: 0.2.2 + sha.js: 2.4.11 + tslib: 2.8.1 + uuid: 9.0.1 + yargs: 17.7.2 + optionalDependencies: + mysql2: 3.11.4 + pg: 8.13.1 + ts-node: 10.9.2(@types/node@20.17.6)(typescript@5.6.3) + transitivePeerDependencies: + - supports-color + + typescript@5.6.3: {} + + uid@2.0.2: + dependencies: + '@lukeed/csprng': 1.1.0 + + undici-types@6.19.8: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.1.1(browserslist@4.24.2): + dependencies: + browserslist: 4.24.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + util-deprecate@1.0.2: {} + + utils-merge@1.0.1: {} + + uuid@9.0.1: {} + + v8-compile-cache-lib@3.0.1: {} + + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + validator@13.12.0: {} + + vary@1.1.2: {} + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + watchpack@2.4.2: + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + webidl-conversions@3.0.1: {} + + webpack-node-externals@3.0.0: {} + + webpack-sources@3.2.3: {} + + webpack@5.96.1: + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.6 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.14.0 + browserslist: 4.24.2 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.17.1 + es-module-lexer: 1.5.4 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.2.1 + terser-webpack-plugin: 5.3.10(webpack@5.96.1) + watchpack: 2.4.2 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + xtend@4.0.2: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yaml@2.6.0: {} + + yargs-parser@20.2.9: {} + + yargs-parser@21.1.1: {} + + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yn@3.1.1: {} + + yocto-queue@0.1.0: {} diff --git a/src/app.module.ts b/src/app.module.ts index 7cec292..b9bfb62 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,9 +1,40 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; +import { AuthModule } from './auth/auth.module'; +import { ConfigModule } from '@nestjs/config'; +import { dotenvConfig } from './shared/configs/dotenv.config'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ConfigService } from '@nestjs/config'; +import { databaseConfig } from './shared/configs/database.config'; +import { GLOBAL_CONFIG } from './shared/constants/global-config.constant'; +import { JwtModule } from '@nestjs/jwt'; +import { DatabaseModule } from './database/database.module'; +import { UserModule } from './user/user.module'; @Module({ - imports: [], + imports: [ + AuthModule, + ConfigModule.forRoot({ + isGlobal: true, + validationSchema: dotenvConfig, + }), + TypeOrmModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + ...databaseConfig, + migrations: ["dist/database/migrations/*.js"], + migrationsRun: true, + synchronize: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), + }), + inject: [ConfigService], + }), + JwtModule.register({ + global: true, + }), + DatabaseModule, + UserModule, + ], controllers: [AppController], providers: [AppService], }) diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts new file mode 100644 index 0000000..f44deea --- /dev/null +++ b/src/auth/auth.controller.ts @@ -0,0 +1,26 @@ +import { Controller, Injectable, Post, Body } from "@nestjs/common"; +import { AuthService } from "./auth.service"; +import { LoginDto } from "./dtos/login.dto"; +import { RegisterDto } from "./dtos/register.dto"; + +@Controller("auth") +@Injectable() +export class AuthController { + constructor( + private readonly authService: AuthService, + ) { } + + @Post("login") + async login( + @Body() loginDto: LoginDto, + ) { + return this.authService.login(loginDto); + } + + @Post("register") + async register( + @Body() registerDto: RegisterDto, + ) { + return this.authService.register(registerDto); + } +} \ No newline at end of file diff --git a/src/auth/auth.guard.ts b/src/auth/auth.guard.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts new file mode 100644 index 0000000..2712cc5 --- /dev/null +++ b/src/auth/auth.module.ts @@ -0,0 +1,13 @@ +import { Module } from "@nestjs/common"; +import { AuthController } from "./auth.controller"; +import { AuthService } from "./auth.service"; +import { UserModule } from "src/user/user.module"; + +@Module({ + imports: [ + UserModule, + ], + controllers: [AuthController], + providers: [AuthService], +}) +export class AuthModule { } \ No newline at end of file diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts new file mode 100644 index 0000000..5829ae0 --- /dev/null +++ b/src/auth/auth.service.ts @@ -0,0 +1,61 @@ +import { Injectable, NotFoundException, BadRequestException } from "@nestjs/common"; +import { ConfigService } from "@nestjs/config"; +import { JwtService } from "@nestjs/jwt"; +import { LoginDto } from "./dtos/login.dto"; +import { RegisterDto } from "./dtos/register.dto"; +import { UserService } from "src/user/user.service"; +import { hash, verify } from "argon2"; +import { AuthResponseDto } from "./dtos/auth-response.dto"; +import { JwtPayloadDto } from "./dtos/jwt-payload.dto"; +import { GLOBAL_CONFIG } from "src/shared/constants/global-config.constant"; +import { UserResponseDto } from "src/user/dtos/user-response.dto"; + +@Injectable() +export class AuthService { + constructor( + private readonly configService: ConfigService, + private readonly jwtService: JwtService, + private readonly userService: UserService, + ) { } + + async login(loginDto: LoginDto): Promise { + const user = await this.userService.findOne({ where: { email: loginDto.email } }); + if (!user) + throw new NotFoundException("User not found"); + const isPasswordValid = await verify(user.password, loginDto.password); + if (!isPasswordValid) + throw new BadRequestException("Invalid password"); + return { + accessToken: this.generateAccessToken({ id: user.id, role: user.role }), + refreshToken: this.generateRefreshToken(), + user: new UserResponseDto(user), + } + } + + async register(registerDto: RegisterDto): Promise { + const user = await this.userService.findOne({ where: { email: registerDto.email } }); + if (user) + throw new BadRequestException("User already exists"); + const hashedPassword = await hash(registerDto.password); + const createdUser = await this.userService.create({ ...registerDto, password: hashedPassword }); + return { + accessToken: this.generateAccessToken({ id: createdUser.id, role: createdUser.role }), + refreshToken: this.generateRefreshToken(), + user: new UserResponseDto(createdUser), + } + } + + generateAccessToken(payload: JwtPayloadDto): string { + return this.jwtService.sign(payload, { + secret: this.configService.get(GLOBAL_CONFIG.JWT_ACCESS_SECRET), + expiresIn: this.configService.get(GLOBAL_CONFIG.JWT_ACCESS_EXPIRATION), + }); + } + + generateRefreshToken(): string { + return this.jwtService.sign(null, { + secret: this.configService.get(GLOBAL_CONFIG.JWT_REFRESH_SECRET), + expiresIn: this.configService.get(GLOBAL_CONFIG.JWT_REFRESH_EXPIRATION), + }); + } +} \ No newline at end of file diff --git a/src/auth/dtos/auth-response.dto.ts b/src/auth/dtos/auth-response.dto.ts new file mode 100644 index 0000000..82538da --- /dev/null +++ b/src/auth/dtos/auth-response.dto.ts @@ -0,0 +1,7 @@ +import { UserResponseDto } from "src/user/dtos/user-response.dto"; + +export class AuthResponseDto { + accessToken: string; + refreshToken: string; + user: UserResponseDto; +} \ No newline at end of file diff --git a/src/auth/dtos/jwt-payload.dto.ts b/src/auth/dtos/jwt-payload.dto.ts new file mode 100644 index 0000000..2319d17 --- /dev/null +++ b/src/auth/dtos/jwt-payload.dto.ts @@ -0,0 +1,6 @@ +import { Roles } from "src/shared/enums/roles.enum"; + +export class JwtPayloadDto { + id: string; + role: Roles; +} \ No newline at end of file diff --git a/src/auth/dtos/login.dto.ts b/src/auth/dtos/login.dto.ts new file mode 100644 index 0000000..e6242d6 --- /dev/null +++ b/src/auth/dtos/login.dto.ts @@ -0,0 +1,9 @@ +import { IsString, IsEmail, IsStrongPassword } from 'class-validator'; + +export class LoginDto { + @IsEmail() + email: string; + + @IsString() + password: string; +} \ No newline at end of file diff --git a/src/auth/dtos/register.dto.ts b/src/auth/dtos/register.dto.ts new file mode 100644 index 0000000..1f20e7a --- /dev/null +++ b/src/auth/dtos/register.dto.ts @@ -0,0 +1,3 @@ +import { CreateUserDto } from "src/user/dtos/create-user.dto"; + +export class RegisterDto extends CreateUserDto { } \ No newline at end of file diff --git a/src/database/database.module.ts b/src/database/database.module.ts new file mode 100644 index 0000000..f504a09 --- /dev/null +++ b/src/database/database.module.ts @@ -0,0 +1,8 @@ +import { Module } from "@nestjs/common"; +import { databaseProviders } from "./database.provoders"; + +@Module({ + providers: [...databaseProviders], + exports: [...databaseProviders] +}) +export class DatabaseModule { } \ No newline at end of file diff --git a/src/database/database.provoders.ts b/src/database/database.provoders.ts new file mode 100644 index 0000000..a425edd --- /dev/null +++ b/src/database/database.provoders.ts @@ -0,0 +1,11 @@ +import { DataSource } from "typeorm"; +import { databaseConfig } from "../shared/configs/database.config"; + +export const databaseProviders = [ + { + provide: "DataSource", + useFactory: async () => { + return await new DataSource(databaseConfig).initialize(); + }, + }, +]; \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 484c87b..f5b1674 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,8 +1,29 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; +import { ValidationPipe } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { createDatabase } from 'typeorm-extension'; +import { databaseConfig } from './shared/configs/database.config'; +import { GLOBAL_CONFIG } from './shared/constants/global-config.constant'; async function bootstrap() { + const configService = new ConfigService() + + await createDatabase({ + options: databaseConfig, + }); + const app = await NestFactory.create(AppModule); - await app.listen(process.env.PORT ?? 3000); + + app.useGlobalPipes(new ValidationPipe()); + app.enableCors({ + origin: configService.get(GLOBAL_CONFIG.CORS_ALLOW_ORIGIN), + methods: ['GET', 'POST', 'PATCH', 'DELETE'], + optionsSuccessStatus: 200, + exposedHeaders: "Authorization", + }); + + await app.listen(configService.get(GLOBAL_CONFIG.PORT) ?? 3000); } -bootstrap(); + +bootstrap(); \ No newline at end of file diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts new file mode 100644 index 0000000..bf3be8a --- /dev/null +++ b/src/shared/configs/database.config.ts @@ -0,0 +1,17 @@ +import { DataSourceOptions } from "typeorm"; +import { ConfigService } from "@nestjs/config"; +import { GLOBAL_CONFIG } from "../constants/global-config.constant"; +import 'dotenv/config'; + +const configService = new ConfigService(); + +export const databaseConfig: DataSourceOptions = { + type: "postgres", + host: configService.get(GLOBAL_CONFIG.DB_HOST), + port: configService.get(GLOBAL_CONFIG.DB_PORT), + username: configService.get(GLOBAL_CONFIG.DB_USERNAME), + password: configService.get(GLOBAL_CONFIG.DB_PASSWORD), + database: configService.get(GLOBAL_CONFIG.DB_DATABASE), + logging: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), + // entities: [Scheduler, Files, Screen, User], +}; \ No newline at end of file diff --git a/src/shared/configs/dotenv.config.ts b/src/shared/configs/dotenv.config.ts new file mode 100644 index 0000000..24a71bd --- /dev/null +++ b/src/shared/configs/dotenv.config.ts @@ -0,0 +1,24 @@ +import * as Joi from 'joi'; +import { Environment } from '../enums/environment.enum'; + +export const dotenvConfig = Joi.object({ + PORT: Joi.number().required(), + NODE_ENV: Joi.string() + .valid(...Object.values(Environment)) + .default(Environment.DEVELOPMENT), + IS_DEVELOPMENT: Joi.boolean().when('NODE_ENV', { + is: Joi.equal(Environment.DEVELOPMENT), + then: Joi.boolean().default(true), + otherwise: Joi.boolean().default(false), + }), + DB_HOST: Joi.string().required(), + DB_PORT: Joi.number().required(), + DB_USERNAME: Joi.string().required(), + DB_PASSWORD: Joi.string().required(), + DB_DATABASE: Joi.string().required(), + CORS_ALLOW_ORIGIN: Joi.string().required(), + JWT_ACCESS_SECRET: Joi.string().required(), + JWT_REFRESH_SECRET: Joi.string().required(), + JWT_ACCESS_EXPIRATION: Joi.string().required(), + JWT_REFRESH_EXPIRATION: Joi.string().required(), +}); diff --git a/src/shared/constants/global-config.constant.ts b/src/shared/constants/global-config.constant.ts new file mode 100644 index 0000000..bed622e --- /dev/null +++ b/src/shared/constants/global-config.constant.ts @@ -0,0 +1,15 @@ +export const GLOBAL_CONFIG = { + PORT: "PORT", + NODE_ENV: "NODE_ENV", + IS_DEVELOPMENT: "IS_DEVELOPMENT", + DB_HOST: "DB_HOST", + DB_PORT: "DB_PORT", + DB_USERNAME: "DB_USERNAME", + DB_PASSWORD: "DB_PASSWORD", + DB_DATABASE: "DB_DATABASE", + CORS_ALLOW_ORIGIN: "CORS_ALLOW_ORIGIN", + JWT_ACCESS_SECRET: "JWT_SCREEN_ACCESS_SECRET", + JWT_REFRESH_SECRET: "JWT_USER_SECRET", + JWT_ACCESS_EXPIRATION: "JWT_ACCESS_EXPIRATION", + JWT_REFRESH_EXPIRATION: "JWT_REFRESH_EXPIRATION", +}; \ No newline at end of file diff --git a/src/shared/enums/environment.enum.ts b/src/shared/enums/environment.enum.ts new file mode 100644 index 0000000..1ff20ee --- /dev/null +++ b/src/shared/enums/environment.enum.ts @@ -0,0 +1,4 @@ +export enum Environment { + DEVELOPMENT = "development", + PRODUCTION = "production", +} \ No newline at end of file diff --git a/src/shared/enums/roles.enum.ts b/src/shared/enums/roles.enum.ts new file mode 100644 index 0000000..9f5296f --- /dev/null +++ b/src/shared/enums/roles.enum.ts @@ -0,0 +1,5 @@ +export enum Roles { + ADMIN = "admin", + STUDENT = "student", + TEACHER = "teacher", +} \ No newline at end of file diff --git a/src/user/dtos/create-user.dto.ts b/src/user/dtos/create-user.dto.ts new file mode 100644 index 0000000..a8d05c8 --- /dev/null +++ b/src/user/dtos/create-user.dto.ts @@ -0,0 +1,16 @@ +import { IsString, IsEmail, IsStrongPassword, IsEnum } from "class-validator"; +import { Roles } from "src/shared/enums/roles.enum"; + +export class CreateUserDto { + @IsEmail() + email: string; + + @IsString() + username: string; + + @IsStrongPassword() + password: string; + + @IsEnum(Roles) + role: Roles; +} \ No newline at end of file diff --git a/src/user/dtos/update-user.dto.ts b/src/user/dtos/update-user.dto.ts new file mode 100644 index 0000000..efca9b7 --- /dev/null +++ b/src/user/dtos/update-user.dto.ts @@ -0,0 +1,11 @@ +import { IsString, IsOptional } from "class-validator" + +export class UpdateUserDto { + @IsString() + @IsOptional() + username?: string; + + @IsString() + @IsOptional() + password?: string; +} \ No newline at end of file diff --git a/src/user/dtos/user-response.dto.ts b/src/user/dtos/user-response.dto.ts new file mode 100644 index 0000000..cefb95f --- /dev/null +++ b/src/user/dtos/user-response.dto.ts @@ -0,0 +1,18 @@ +import { Roles } from "src/shared/enums/roles.enum"; +import { User } from "../user.entity"; + +export class UserResponseDto { + id: string; + email: string; + role: Roles; + createdAt: Date; + updatedAt: Date; + + constructor(user: User) { + this.id = user.id; + this.email = user.email; + this.role = user.role; + this.createdAt = user.createdAt; + this.updatedAt = user.updatedAt; + } +} \ No newline at end of file diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts new file mode 100644 index 0000000..3445f26 --- /dev/null +++ b/src/user/user.controller.ts @@ -0,0 +1,9 @@ +import { Controller, Injectable } from "@nestjs/common"; + +@Controller("user") +@Injectable() +export class UserController { + constructor() { } + + +} diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts new file mode 100644 index 0000000..393a33b --- /dev/null +++ b/src/user/user.entity.ts @@ -0,0 +1,49 @@ +import { Entity } from "typeorm"; +import { Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"; +import { Roles } from "src/shared/enums/roles.enum"; + +@Entity() +export class User { + @PrimaryGeneratedColumn("uuid") + id: string; + + @Column({ + type: "string", + nullable: false, + unique: true, + }) + username: string; + + @Column({ + type: "enum", + enum: Roles, + nullable: false, + }) + role: Roles; + + @Column({ + type: "string", + nullable: false, + unique: true, + }) + password: string; + + @Column({ + type: "string", + nullable: false, + unique: true, + }) + email: string; + + @CreateDateColumn({ + type: "timestamp", + nullable: false, + }) + createdAt: Date; + + @UpdateDateColumn({ + type: "timestamp", + nullable: false, + }) + updatedAt: Date; +} \ No newline at end of file diff --git a/src/user/user.module.ts b/src/user/user.module.ts new file mode 100644 index 0000000..fa96c35 --- /dev/null +++ b/src/user/user.module.ts @@ -0,0 +1,18 @@ +import { Module } from "@nestjs/common"; +import { UserController } from "./user.controller"; +import { UserService } from "./user.service"; +import { userProviders } from "./user.providers"; +import { DatabaseModule } from "src/database/database.module"; + +@Module({ + imports: [ + DatabaseModule, + ], + controllers: [UserController], + providers: [ + ...userProviders, + UserService + ], + exports: [UserService], +}) +export class UserModule { } \ No newline at end of file diff --git a/src/user/user.providers.ts b/src/user/user.providers.ts new file mode 100644 index 0000000..5e892de --- /dev/null +++ b/src/user/user.providers.ts @@ -0,0 +1,8 @@ +import { DataSource } from "typeorm"; +import { User } from "./user.entity"; + +export const userProviders = [{ + provide: 'UserRepository', + useFactory: (dataSource: DataSource) => dataSource.getRepository(User), + inject: ['DataSource'], +}]; \ No newline at end of file diff --git a/src/user/user.service.ts b/src/user/user.service.ts new file mode 100644 index 0000000..81ba042 --- /dev/null +++ b/src/user/user.service.ts @@ -0,0 +1,37 @@ +import { Injectable, Inject, NotFoundException } from "@nestjs/common"; +import { Repository } from "typeorm"; +import { User } from "./user.entity"; +import { UpdateUserDto } from "./dtos/update-user.dto"; +import { FindOneOptions } from "typeorm"; +import { CreateUserDto } from "./dtos/create-user.dto"; + +@Injectable() +export class UserService { + constructor( + @Inject("UserRepository") + private readonly userRepository: Repository, + ) { } + + async findAll(): Promise { + return this.userRepository.find(); + } + + async findOne(option: FindOneOptions): Promise { + return this.userRepository.findOne(option); + } + + async create(createUserDto: CreateUserDto): Promise { + return this.userRepository.save(createUserDto); + } + + async update(id: string, updateUserDto: UpdateUserDto): Promise { + try { + await this.userRepository.update(id, updateUserDto); + return await this.findOne({ where: { id } }); + } catch (error) { + if (error instanceof Error) { + throw new NotFoundException("User not found"); + } + } + } +} \ No newline at end of file diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts new file mode 100644 index 0000000..50cda62 --- /dev/null +++ b/test/app.e2e-spec.ts @@ -0,0 +1,24 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { INestApplication } from '@nestjs/common'; +import * as request from 'supertest'; +import { AppModule } from './../src/app.module'; + +describe('AppController (e2e)', () => { + let app: INestApplication; + + beforeEach(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + it('/ (GET)', () => { + return request(app.getHttpServer()) + .get('/') + .expect(200) + .expect('Hello World!'); + }); +}); diff --git a/test/jest-e2e.json b/test/jest-e2e.json new file mode 100644 index 0000000..e9d912f --- /dev/null +++ b/test/jest-e2e.json @@ -0,0 +1,9 @@ +{ + "moduleFileExtensions": ["js", "json", "ts"], + "rootDir": ".", + "testEnvironment": "node", + "testRegex": ".e2e-spec.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + } +} From f88310a9650ac337c139fb1e64bda0a82aa62548 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Fri, 8 Nov 2024 22:48:51 +0700 Subject: [PATCH 005/155] chore: Add TypeOrmModule.forFeature for User entity --- src/app.module.ts | 3 +++ src/auth/dtos/login.dto.ts | 4 +++- src/shared/configs/database.config.ts | 3 ++- src/user/dtos/create-user.dto.ts | 6 +++++- src/user/user.entity.ts | 3 --- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/app.module.ts b/src/app.module.ts index b9bfb62..52085ea 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -11,6 +11,9 @@ import { GLOBAL_CONFIG } from './shared/constants/global-config.constant'; import { JwtModule } from '@nestjs/jwt'; import { DatabaseModule } from './database/database.module'; import { UserModule } from './user/user.module'; +import { User } from './user/user.entity'; + +const forFeatures = TypeOrmModule.forFeature([User]); @Module({ imports: [ diff --git a/src/auth/dtos/login.dto.ts b/src/auth/dtos/login.dto.ts index e6242d6..a2708a4 100644 --- a/src/auth/dtos/login.dto.ts +++ b/src/auth/dtos/login.dto.ts @@ -1,9 +1,11 @@ -import { IsString, IsEmail, IsStrongPassword } from 'class-validator'; +import { IsString, IsEmail, IsNotEmpty } from 'class-validator'; export class LoginDto { @IsEmail() + @IsNotEmpty() email: string; @IsString() + @IsNotEmpty() password: string; } \ No newline at end of file diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index bf3be8a..2258985 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -2,6 +2,7 @@ import { DataSourceOptions } from "typeorm"; import { ConfigService } from "@nestjs/config"; import { GLOBAL_CONFIG } from "../constants/global-config.constant"; import 'dotenv/config'; +import { User } from "src/user/user.entity"; const configService = new ConfigService(); @@ -13,5 +14,5 @@ export const databaseConfig: DataSourceOptions = { password: configService.get(GLOBAL_CONFIG.DB_PASSWORD), database: configService.get(GLOBAL_CONFIG.DB_DATABASE), logging: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), - // entities: [Scheduler, Files, Screen, User], + entities: [User], }; \ No newline at end of file diff --git a/src/user/dtos/create-user.dto.ts b/src/user/dtos/create-user.dto.ts index a8d05c8..70541c0 100644 --- a/src/user/dtos/create-user.dto.ts +++ b/src/user/dtos/create-user.dto.ts @@ -1,16 +1,20 @@ -import { IsString, IsEmail, IsStrongPassword, IsEnum } from "class-validator"; +import { IsString, IsEmail, IsStrongPassword, IsEnum, IsNotEmpty } from "class-validator"; import { Roles } from "src/shared/enums/roles.enum"; export class CreateUserDto { @IsEmail() + @IsNotEmpty() email: string; @IsString() + @IsNotEmpty() username: string; @IsStrongPassword() + @IsNotEmpty() password: string; @IsEnum(Roles) + @IsNotEmpty() role: Roles; } \ No newline at end of file diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index 393a33b..c50b64f 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -8,7 +8,6 @@ export class User { id: string; @Column({ - type: "string", nullable: false, unique: true, }) @@ -22,14 +21,12 @@ export class User { role: Roles; @Column({ - type: "string", nullable: false, unique: true, }) password: string; @Column({ - type: "string", nullable: false, unique: true, }) From af93b58a1a191663d331af5ffd6bcf7aeb35b7df Mon Sep 17 00:00:00 2001 From: ganthepro Date: Sat, 9 Nov 2024 20:11:35 +0700 Subject: [PATCH 006/155] chore: Update database host in .env.example and .env file --- .env.example | 2 +- .gitignore | 2 + Dockerfile | 28 +++++++++++ docker-compose.yml | 36 +++++++++++++ package.json | 1 + pnpm-lock.yaml | 3 ++ src/app.module.ts | 11 +++- src/auth/auth.controller.ts | 3 ++ src/auth/auth.guard.ts | 50 +++++++++++++++++++ src/auth/auth.service.ts | 36 ++++++++----- .../authenticated-request.interface.ts | 5 ++ src/database/database.module.ts | 2 +- ...ase.provoders.ts => database.providers.ts} | 0 .../constants/global-config.constant.ts | 4 +- src/shared/decorators/public.decorator.ts | 5 ++ src/user/dtos/create-user.dto.ts | 15 +++++- src/user/dtos/user-response.dto.ts | 2 + src/user/user.controller.ts | 48 ++++++++++++++++-- src/user/user.entity.ts | 7 ++- src/user/user.service.ts | 19 +++++-- 20 files changed, 252 insertions(+), 27 deletions(-) create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 src/auth/interfaces/authenticated-request.interface.ts rename src/database/{database.provoders.ts => database.providers.ts} (100%) create mode 100644 src/shared/decorators/public.decorator.ts diff --git a/.env.example b/.env.example index 3bdfe5b..f4e5643 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,6 @@ PORT=8080 NODE_ENV=development -DB_HOST=localhost +DB_HOST=edusaig-db DB_PORT=5432 DB_USERNAME=postgres DB_PASSWORD=qwerty diff --git a/.gitignore b/.gitignore index 43ad4b2..3bb7fb8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # compiled output /dist /node_modules +/postgres-data # Logs logs @@ -10,6 +11,7 @@ pnpm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* +pnpm-lock.yaml # OS .DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b27f0cc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,28 @@ +FROM node:20-alpine AS base + +RUN npm i -g pnpm + +FROM base as dependencies +WORKDIR /app +COPY package.json pnpm-lock.yaml ./ +RUN pnpm install --frozen-lockfile + +FROM base AS build +WORKDIR /app +COPY . . +COPY --from=dependencies /app/node_modules ./node_modules +RUN pnpm run build +RUN pnpm prune --prod + +FROM base AS deploy +RUN apk add --update curl && rm -rf /var/cache/apk/* + +WORKDIR /app +COPY --from=build --chown=node:node /app/dist ./dist +COPY --from=build --chown=node:node /app/node_modules ./node_modules +COPY --chown=node:node .env .env + +USER node + +EXPOSE 3000 +CMD ["node", "dist/main.js"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..b945076 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +version: '3.8' + +services: + db: + image: postgres:alpine3.20 + container_name: ${DB_HOST} + restart: unless-stopped + ports: + - '${DB_PORT}:${DB_PORT}' + environment: + POSTGRES_USER: ${DB_USERNAME} + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_DB: ${DB_DATABASE} + volumes: + - ./postgres-data:/var/lib/postgresql/data + healthcheck: + test: ['CMD-SHELL', 'pg_isready -U ${DB_USERNAME}'] + interval: 10s + timeout: 5s + retries: 5 + + app: + container_name: edusaig-api + image: edusaig-api + depends_on: + db: + condition: service_healthy + build: + context: . + restart: unless-stopped + env_file: + - .env + environment: + - TZ=Asia/Bangkok + ports: + - '${PORT}:${PORT}' diff --git a/package.json b/package.json index 8890557..afe4fad 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "argon2": "^0.41.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "dotenv": "^16.4.5", "joi": "^17.13.3", "mysql2": "^3.11.4", "pg": "^8.13.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f9417da..98c7996 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: class-validator: specifier: ^0.14.1 version: 0.14.1 + dotenv: + specifier: ^16.4.5 + version: 16.4.5 joi: specifier: ^17.13.3 version: 17.13.3 diff --git a/src/app.module.ts b/src/app.module.ts index 52085ea..818993a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -12,11 +12,14 @@ import { JwtModule } from '@nestjs/jwt'; import { DatabaseModule } from './database/database.module'; import { UserModule } from './user/user.module'; import { User } from './user/user.entity'; +import { APP_GUARD } from '@nestjs/core'; +import { AuthGuard } from './auth/auth.guard'; const forFeatures = TypeOrmModule.forFeature([User]); @Module({ imports: [ + forFeatures, AuthModule, ConfigModule.forRoot({ isGlobal: true, @@ -39,6 +42,12 @@ const forFeatures = TypeOrmModule.forFeature([User]); UserModule, ], controllers: [AppController], - providers: [AppService], + providers: [ + AppService, + { + provide: APP_GUARD, + useClass: AuthGuard, + } + ], }) export class AppModule { } diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index f44deea..844092f 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -2,6 +2,7 @@ import { Controller, Injectable, Post, Body } from "@nestjs/common"; import { AuthService } from "./auth.service"; import { LoginDto } from "./dtos/login.dto"; import { RegisterDto } from "./dtos/register.dto"; +import { Public } from "src/shared/decorators/public.decorator"; @Controller("auth") @Injectable() @@ -11,6 +12,7 @@ export class AuthController { ) { } @Post("login") + @Public() async login( @Body() loginDto: LoginDto, ) { @@ -18,6 +20,7 @@ export class AuthController { } @Post("register") + @Public() async register( @Body() registerDto: RegisterDto, ) { diff --git a/src/auth/auth.guard.ts b/src/auth/auth.guard.ts index e69de29..ccf63e0 100644 --- a/src/auth/auth.guard.ts +++ b/src/auth/auth.guard.ts @@ -0,0 +1,50 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + UnauthorizedException, +} from "@nestjs/common"; +import { JwtService } from "@nestjs/jwt"; +import { ConfigService } from "@nestjs/config"; +import { GLOBAL_CONFIG } from "src/shared/constants/global-config.constant"; +import { JwtPayloadDto } from "./dtos/jwt-payload.dto"; +import { IS_PUBLIC_KEY } from "src/shared/decorators/public.decorator"; +import { Reflector } from "@nestjs/core"; +import { Request } from "express"; + +@Injectable() +export class AuthGuard implements CanActivate { + constructor( + private readonly jwtService: JwtService, + private readonly configService: ConfigService, + private readonly reflector: Reflector + ) {} + + async canActivate(context: ExecutionContext): Promise { + const isPublic = this.reflector.getAllAndOverride( + IS_PUBLIC_KEY, + [context.getHandler(), context.getClass()] + ); + if (isPublic) return true; + const request = context.switchToHttp().getRequest(); + const token = this.extractTokenFromHeader(request); + if (!token) + throw new UnauthorizedException("Unauthorized access"); + try { + request.user = + await this.jwtService.verifyAsync(token, { + secret: this.configService.get( + GLOBAL_CONFIG.JWT_ACCESS_SECRET + ), + }); + } catch (error) { + throw new UnauthorizedException("Unauthorized access"); + } + return true; + } + + private extractTokenFromHeader(request: Request): string | undefined { + const [type, token] = request.headers.authorization?.split(" ") ?? []; + return type === "Bearer" ? token : undefined; + } +} diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 5829ae0..f00160d 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -1,4 +1,4 @@ -import { Injectable, NotFoundException, BadRequestException } from "@nestjs/common"; +import { Injectable, NotFoundException, BadRequestException, InternalServerErrorException } from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { JwtService } from "@nestjs/jwt"; import { LoginDto } from "./dtos/login.dto"; @@ -20,28 +20,40 @@ export class AuthService { async login(loginDto: LoginDto): Promise { const user = await this.userService.findOne({ where: { email: loginDto.email } }); - if (!user) + if (!user) throw new NotFoundException("User not found"); const isPasswordValid = await verify(user.password, loginDto.password); if (!isPasswordValid) throw new BadRequestException("Invalid password"); - return { - accessToken: this.generateAccessToken({ id: user.id, role: user.role }), - refreshToken: this.generateRefreshToken(), - user: new UserResponseDto(user), + try { + return { + accessToken: this.generateAccessToken({ id: user.id, role: user.role }), + refreshToken: this.generateRefreshToken(), + user: new UserResponseDto(user), + } + } catch (error) { + if (error instanceof Error) + throw new InternalServerErrorException(error.message); } } async register(registerDto: RegisterDto): Promise { const user = await this.userService.findOne({ where: { email: registerDto.email } }); - if (user) + if (user) throw new BadRequestException("User already exists"); const hashedPassword = await hash(registerDto.password); const createdUser = await this.userService.create({ ...registerDto, password: hashedPassword }); - return { - accessToken: this.generateAccessToken({ id: createdUser.id, role: createdUser.role }), - refreshToken: this.generateRefreshToken(), - user: new UserResponseDto(createdUser), + try { + return { + accessToken: this.generateAccessToken({ id: createdUser.id, role: createdUser.role }), + refreshToken: this.generateRefreshToken(), + user: new UserResponseDto(createdUser), + } + } catch (error) { + if (error instanceof Error) { + await this.userService.delete({ id: createdUser.id }); + throw new InternalServerErrorException(error.message); + } } } @@ -53,7 +65,7 @@ export class AuthService { } generateRefreshToken(): string { - return this.jwtService.sign(null, { + return this.jwtService.sign({}, { secret: this.configService.get(GLOBAL_CONFIG.JWT_REFRESH_SECRET), expiresIn: this.configService.get(GLOBAL_CONFIG.JWT_REFRESH_EXPIRATION), }); diff --git a/src/auth/interfaces/authenticated-request.interface.ts b/src/auth/interfaces/authenticated-request.interface.ts new file mode 100644 index 0000000..7a2d028 --- /dev/null +++ b/src/auth/interfaces/authenticated-request.interface.ts @@ -0,0 +1,5 @@ +import { JwtPayloadDto } from "../dtos/jwt-payload.dto"; + +export interface AuthenticatedRequest extends Request { + user: JwtPayloadDto; +} diff --git a/src/database/database.module.ts b/src/database/database.module.ts index f504a09..b0af886 100644 --- a/src/database/database.module.ts +++ b/src/database/database.module.ts @@ -1,5 +1,5 @@ import { Module } from "@nestjs/common"; -import { databaseProviders } from "./database.provoders"; +import { databaseProviders } from "./database.providers"; @Module({ providers: [...databaseProviders], diff --git a/src/database/database.provoders.ts b/src/database/database.providers.ts similarity index 100% rename from src/database/database.provoders.ts rename to src/database/database.providers.ts diff --git a/src/shared/constants/global-config.constant.ts b/src/shared/constants/global-config.constant.ts index bed622e..4127d1a 100644 --- a/src/shared/constants/global-config.constant.ts +++ b/src/shared/constants/global-config.constant.ts @@ -8,8 +8,8 @@ export const GLOBAL_CONFIG = { DB_PASSWORD: "DB_PASSWORD", DB_DATABASE: "DB_DATABASE", CORS_ALLOW_ORIGIN: "CORS_ALLOW_ORIGIN", - JWT_ACCESS_SECRET: "JWT_SCREEN_ACCESS_SECRET", - JWT_REFRESH_SECRET: "JWT_USER_SECRET", + JWT_ACCESS_SECRET: "JWT_ACCESS_SECRET", + JWT_REFRESH_SECRET: "JWT_REFRESH_SECRET", JWT_ACCESS_EXPIRATION: "JWT_ACCESS_EXPIRATION", JWT_REFRESH_EXPIRATION: "JWT_REFRESH_EXPIRATION", }; \ No newline at end of file diff --git a/src/shared/decorators/public.decorator.ts b/src/shared/decorators/public.decorator.ts new file mode 100644 index 0000000..f0d4ec5 --- /dev/null +++ b/src/shared/decorators/public.decorator.ts @@ -0,0 +1,5 @@ +import { CustomDecorator, SetMetadata } from "@nestjs/common"; + +export const IS_PUBLIC_KEY = "isPublic"; +export const Public = (): CustomDecorator => + SetMetadata(IS_PUBLIC_KEY, true); diff --git a/src/user/dtos/create-user.dto.ts b/src/user/dtos/create-user.dto.ts index 70541c0..b9ad3f5 100644 --- a/src/user/dtos/create-user.dto.ts +++ b/src/user/dtos/create-user.dto.ts @@ -1,6 +1,11 @@ import { IsString, IsEmail, IsStrongPassword, IsEnum, IsNotEmpty } from "class-validator"; import { Roles } from "src/shared/enums/roles.enum"; +enum AvailableRoles { + STUDENT = Roles.STUDENT, + TEACHER = Roles.TEACHER, +} + export class CreateUserDto { @IsEmail() @IsNotEmpty() @@ -10,11 +15,17 @@ export class CreateUserDto { @IsNotEmpty() username: string; + @IsString() + @IsNotEmpty() + fullname: string; + @IsStrongPassword() @IsNotEmpty() password: string; - @IsEnum(Roles) + @IsEnum(AvailableRoles, { + message: `Invalid role. Role should be either ${Roles.STUDENT} or ${Roles.TEACHER}`, + }) @IsNotEmpty() - role: Roles; + role: Roles.STUDENT | Roles.TEACHER; } \ No newline at end of file diff --git a/src/user/dtos/user-response.dto.ts b/src/user/dtos/user-response.dto.ts index cefb95f..9354455 100644 --- a/src/user/dtos/user-response.dto.ts +++ b/src/user/dtos/user-response.dto.ts @@ -7,6 +7,7 @@ export class UserResponseDto { role: Roles; createdAt: Date; updatedAt: Date; + fullname: string; constructor(user: User) { this.id = user.id; @@ -14,5 +15,6 @@ export class UserResponseDto { this.role = user.role; this.createdAt = user.createdAt; this.updatedAt = user.updatedAt; + this.fullname = user.fullname; } } \ No newline at end of file diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 3445f26..ad7693c 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -1,9 +1,49 @@ -import { Controller, Injectable } from "@nestjs/common"; +import { + Controller, + Injectable, + Get, + Req, + Param, + ParseUUIDPipe, + HttpStatus, +} from '@nestjs/common'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { UserService } from './user.service'; +import { UserResponseDto } from './dtos/user-response.dto'; -@Controller("user") +@Controller('user') @Injectable() export class UserController { - constructor() { } + constructor(private readonly userService: UserService) { } - + @Get('profile') + async getProfile( + @Req() request: AuthenticatedRequest, + ): Promise { + const user = await this.userService.findOne({ + where: { id: request.user.id }, + }); + return new UserResponseDto(user); + } + + @Get() + async findAll(): Promise { + const users = await this.userService.findAll(); + return users.map((user) => new UserResponseDto(user)); + } + + @Get(':id') + async findOne( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const user = await this.userService.findOne({ where: { id } }); + return new UserResponseDto(user); + } } diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index c50b64f..db75286 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -13,9 +13,14 @@ export class User { }) username: string; + @Column({ + nullable: false, + }) + fullname: string; + @Column({ type: "enum", - enum: Roles, + enum: [Roles.STUDENT, Roles.TEACHER], nullable: false, }) role: Roles; diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 81ba042..e36e7ac 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -2,7 +2,7 @@ import { Injectable, Inject, NotFoundException } from "@nestjs/common"; import { Repository } from "typeorm"; import { User } from "./user.entity"; import { UpdateUserDto } from "./dtos/update-user.dto"; -import { FindOneOptions } from "typeorm"; +import { FindOneOptions, FindOptionsWhere } from "typeorm"; import { CreateUserDto } from "./dtos/create-user.dto"; @Injectable() @@ -16,8 +16,11 @@ export class UserService { return this.userRepository.find(); } - async findOne(option: FindOneOptions): Promise { - return this.userRepository.findOne(option); + async findOne(options: FindOneOptions): Promise { + const user = this.userRepository.findOne(options); + if (!user) + throw new NotFoundException("User not found"); + return user; } async create(createUserDto: CreateUserDto): Promise { @@ -34,4 +37,14 @@ export class UserService { } } } + + async delete(options: FindOptionsWhere): Promise { + try { + await this.userRepository.delete(options); + } catch (error) { + if (error instanceof Error) { + throw new NotFoundException("User not found"); + } + } + } } \ No newline at end of file From 55801c98f7a9d63513316c23963530420158fb77 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Sat, 9 Nov 2024 21:30:53 +0700 Subject: [PATCH 007/155] chore: Update roles enum and related files --- package.json | 1 + pnpm-lock.yaml | 52 +++++++++++++++++++++++ src/app.controller.ts | 2 + src/auth/auth.controller.ts | 47 ++++++++++++--------- src/auth/auth.service.ts | 2 +- src/auth/dtos/auth-response.dto.ts | 15 +++++++ src/auth/dtos/jwt-payload.dto.ts | 4 +- src/auth/dtos/login.dto.ts | 11 +++++ src/main.ts | 17 ++++++-- src/shared/decorators/role.decorator.ts | 5 +++ src/shared/enums/roles.enum.ts | 2 +- src/shared/guards/role.guard.ts | 21 ++++++++++ src/user/dtos/create-user.dto.ts | 37 +++++++++++++--- src/user/dtos/update-user.dto.ts | 14 ++++++- src/user/dtos/user-response.dto.ts | 41 +++++++++++++++++- src/user/user.controller.ts | 56 +++++++++++++++++++++++++ src/user/user.entity.ts | 6 +-- src/user/user.service.ts | 13 +++--- 18 files changed, 303 insertions(+), 43 deletions(-) create mode 100644 src/shared/decorators/role.decorator.ts create mode 100644 src/shared/guards/role.guard.ts diff --git a/package.json b/package.json index afe4fad..dbc8a66 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@nestjs/jwt": "^10.2.0", "@nestjs/mapped-types": "^2.0.6", "@nestjs/platform-express": "^10.0.0", + "@nestjs/swagger": "^8.0.5", "@nestjs/typeorm": "^10.0.2", "argon2": "^0.41.1", "class-transformer": "^0.5.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 98c7996..dac0a47 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: '@nestjs/platform-express': specifier: ^10.0.0 version: 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7) + '@nestjs/swagger': + specifier: ^8.0.5 + version: 8.0.5(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) '@nestjs/typeorm': specifier: ^10.0.2 version: 10.0.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3))) @@ -474,6 +477,9 @@ packages: resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} engines: {node: '>=8'} + '@microsoft/tsdoc@0.15.0': + resolution: {integrity: sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==} + '@nestjs/class-validator@0.13.1': resolution: {integrity: sha512-Wwpg9KuGnkWOFleBPEFdHQKwqZsF/PFWQaeSaXwatkofgqD7N6AV5J30xIVgScDP+IcE+MdPGZJ38vHQ24KxPQ==} @@ -555,6 +561,23 @@ packages: peerDependencies: typescript: '>=4.8.2' + '@nestjs/swagger@8.0.5': + resolution: {integrity: sha512-ZmBdsbQNs3wIN5kCuvAVbz3/ULh3gi814oHTP49uTqAGi1aT0YSatUyncwQOHBOlRT+rwF+TNjoAsZ+twIk/Jw==} + peerDependencies: + '@fastify/static': ^6.0.0 || ^7.0.0 + '@nestjs/common': ^9.0.0 || ^10.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + '@fastify/static': + optional: true + class-transformer: + optional: true + class-validator: + optional: true + '@nestjs/testing@10.4.7': resolution: {integrity: sha512-aS3sQ0v4g8cyHDzW3xJv1+8MiFAkxUNXmnau588IFFI/nBIo/kevLNHNPr85keYekkJ/lwNDW72h8UGg8BYd9w==} peerDependencies: @@ -606,6 +629,9 @@ packages: resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@scarf/scarf@1.4.0': + resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} + '@sideway/address@4.1.5': resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} @@ -2784,6 +2810,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + swagger-ui-dist@5.18.2: + resolution: {integrity: sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw==} + symbol-observable@4.0.0: resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} engines: {node: '>=0.10'} @@ -3656,6 +3685,8 @@ snapshots: '@lukeed/csprng@1.1.0': {} + '@microsoft/tsdoc@0.15.0': {} + '@nestjs/class-validator@0.13.1': dependencies: '@types/validator': 13.12.2 @@ -3760,6 +3791,21 @@ snapshots: transitivePeerDependencies: - chokidar + '@nestjs/swagger@8.0.5(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)': + dependencies: + '@microsoft/tsdoc': 0.15.0 + '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/mapped-types': 2.0.6(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) + js-yaml: 4.1.0 + lodash: 4.17.21 + path-to-regexp: 3.3.0 + reflect-metadata: 0.2.2 + swagger-ui-dist: 5.18.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.1 + '@nestjs/testing@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@nestjs/platform-express@10.4.7)': dependencies: '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -3804,6 +3850,8 @@ snapshots: '@pkgr/core@0.1.1': {} + '@scarf/scarf@1.4.0': {} + '@sideway/address@4.1.5': dependencies: '@hapi/hoek': 9.3.0 @@ -6271,6 +6319,10 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + swagger-ui-dist@5.18.2: + dependencies: + '@scarf/scarf': 1.4.0 + symbol-observable@4.0.0: {} synckit@0.9.2: diff --git a/src/app.controller.ts b/src/app.controller.ts index edfc45e..d27ee79 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -1,11 +1,13 @@ import { Controller, Get } from '@nestjs/common'; import { AppService } from './app.service'; +import { Public } from './shared/decorators/public.decorator'; @Controller() export class AppController { constructor(private readonly appService: AppService) { } @Get() + @Public() getHello(): string { return this.appService.getHello(); } diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 844092f..b888ba1 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -1,29 +1,36 @@ -import { Controller, Injectable, Post, Body } from "@nestjs/common"; -import { AuthService } from "./auth.service"; -import { LoginDto } from "./dtos/login.dto"; -import { RegisterDto } from "./dtos/register.dto"; -import { Public } from "src/shared/decorators/public.decorator"; +import { Controller, Injectable, Post, Body, HttpCode, HttpStatus } from '@nestjs/common'; +import { AuthService } from './auth.service'; +import { LoginDto } from './dtos/login.dto'; +import { RegisterDto } from './dtos/register.dto'; +import { Public } from 'src/shared/decorators/public.decorator'; +import { ApiTags, ApiResponse } from '@nestjs/swagger'; +import { AuthResponseDto } from './dtos/auth-response.dto'; -@Controller("auth") +@Controller('auth') +@ApiTags('Auth') @Injectable() export class AuthController { - constructor( - private readonly authService: AuthService, - ) { } + constructor(private readonly authService: AuthService) { } - @Post("login") + @Post('login') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Login', + type: AuthResponseDto + }) @Public() - async login( - @Body() loginDto: LoginDto, - ) { - return this.authService.login(loginDto); + async login(@Body() loginDto: LoginDto): Promise { + return await this.authService.login(loginDto); } - @Post("register") + @Post('register') + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Register', + type: AuthResponseDto + }) @Public() - async register( - @Body() registerDto: RegisterDto, - ) { - return this.authService.register(registerDto); + async register(@Body() registerDto: RegisterDto): Promise { + return await this.authService.register(registerDto); } -} \ No newline at end of file +} diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index f00160d..aa64282 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -51,7 +51,7 @@ export class AuthService { } } catch (error) { if (error instanceof Error) { - await this.userService.delete({ id: createdUser.id }); + await this.userService.delete(createdUser.id); throw new InternalServerErrorException(error.message); } } diff --git a/src/auth/dtos/auth-response.dto.ts b/src/auth/dtos/auth-response.dto.ts index 82538da..6fc3440 100644 --- a/src/auth/dtos/auth-response.dto.ts +++ b/src/auth/dtos/auth-response.dto.ts @@ -1,7 +1,22 @@ import { UserResponseDto } from "src/user/dtos/user-response.dto"; +import { ApiProperty } from "@nestjs/swagger"; export class AuthResponseDto { + @ApiProperty({ + description: 'Access token', + type: String, + }) accessToken: string; + + @ApiProperty({ + description: 'Refresh token', + type: String, + }) refreshToken: string; + + @ApiProperty({ + description: 'User', + type: UserResponseDto, + }) user: UserResponseDto; } \ No newline at end of file diff --git a/src/auth/dtos/jwt-payload.dto.ts b/src/auth/dtos/jwt-payload.dto.ts index 2319d17..1544564 100644 --- a/src/auth/dtos/jwt-payload.dto.ts +++ b/src/auth/dtos/jwt-payload.dto.ts @@ -1,6 +1,6 @@ -import { Roles } from "src/shared/enums/roles.enum"; +import { Role } from "src/shared/enums/roles.enum"; export class JwtPayloadDto { id: string; - role: Roles; + role: Role; } \ No newline at end of file diff --git a/src/auth/dtos/login.dto.ts b/src/auth/dtos/login.dto.ts index a2708a4..97fbfd3 100644 --- a/src/auth/dtos/login.dto.ts +++ b/src/auth/dtos/login.dto.ts @@ -1,11 +1,22 @@ import { IsString, IsEmail, IsNotEmpty } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; export class LoginDto { @IsEmail() @IsNotEmpty() + @ApiProperty({ + description: 'User email', + type: String, + example: 'johndoe@gmail.com', + }) email: string; @IsString() @IsNotEmpty() + @ApiProperty({ + description: 'User password', + type: String, + example: 'P@ssw0rd!', + }) password: string; } \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index f5b1674..0917d72 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,9 +5,10 @@ import { ConfigService } from '@nestjs/config'; import { createDatabase } from 'typeorm-extension'; import { databaseConfig } from './shared/configs/database.config'; import { GLOBAL_CONFIG } from './shared/constants/global-config.constant'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; async function bootstrap() { - const configService = new ConfigService() + const configService = new ConfigService(); await createDatabase({ options: databaseConfig, @@ -20,10 +21,20 @@ async function bootstrap() { origin: configService.get(GLOBAL_CONFIG.CORS_ALLOW_ORIGIN), methods: ['GET', 'POST', 'PATCH', 'DELETE'], optionsSuccessStatus: 200, - exposedHeaders: "Authorization", + exposedHeaders: 'Authorization', }); + const config = new DocumentBuilder() + .addBearerAuth() + .setTitle('Cats example') + .setDescription('The cats API description') + .setVersion('1.0') + .addTag('cats') + .build(); + const documentFactory = () => SwaggerModule.createDocument(app, config); + SwaggerModule.setup('docs', app, documentFactory); + await app.listen(configService.get(GLOBAL_CONFIG.PORT) ?? 3000); } -bootstrap(); \ No newline at end of file +bootstrap(); diff --git a/src/shared/decorators/role.decorator.ts b/src/shared/decorators/role.decorator.ts new file mode 100644 index 0000000..45788dc --- /dev/null +++ b/src/shared/decorators/role.decorator.ts @@ -0,0 +1,5 @@ +import { SetMetadata } from '@nestjs/common'; +import { Role } from '../enums/roles.enum'; + +export const ROLES_KEY = 'roles'; +export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles); \ No newline at end of file diff --git a/src/shared/enums/roles.enum.ts b/src/shared/enums/roles.enum.ts index 9f5296f..15dc7ec 100644 --- a/src/shared/enums/roles.enum.ts +++ b/src/shared/enums/roles.enum.ts @@ -1,4 +1,4 @@ -export enum Roles { +export enum Role { ADMIN = "admin", STUDENT = "student", TEACHER = "teacher", diff --git a/src/shared/guards/role.guard.ts b/src/shared/guards/role.guard.ts new file mode 100644 index 0000000..14b4b8e --- /dev/null +++ b/src/shared/guards/role.guard.ts @@ -0,0 +1,21 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { Role } from '../enums/roles.enum'; +import { ROLES_KEY } from '../decorators/role.decorator'; + +@Injectable() +export class RolesGuard implements CanActivate { + constructor(private reflector: Reflector) {} + + canActivate(context: ExecutionContext): boolean { + const requiredRoles = this.reflector.getAllAndOverride(ROLES_KEY, [ + context.getHandler(), + context.getClass(), + ]); + if (!requiredRoles) { + return true; + } + const { user } = context.switchToHttp().getRequest(); + return requiredRoles.some((role) => role === user.role); + } +} \ No newline at end of file diff --git a/src/user/dtos/create-user.dto.ts b/src/user/dtos/create-user.dto.ts index b9ad3f5..be47d18 100644 --- a/src/user/dtos/create-user.dto.ts +++ b/src/user/dtos/create-user.dto.ts @@ -1,31 +1,58 @@ import { IsString, IsEmail, IsStrongPassword, IsEnum, IsNotEmpty } from "class-validator"; -import { Roles } from "src/shared/enums/roles.enum"; +import { Role } from "src/shared/enums/roles.enum"; +import { ApiProperty } from "@nestjs/swagger"; enum AvailableRoles { - STUDENT = Roles.STUDENT, - TEACHER = Roles.TEACHER, + STUDENT = Role.STUDENT, + TEACHER = Role.TEACHER, } export class CreateUserDto { @IsEmail() @IsNotEmpty() + @ApiProperty({ + description: 'User email', + type: String, + example: 'johndoe@gmail.com', + }) email: string; @IsString() @IsNotEmpty() + @ApiProperty({ + description: 'User username', + type: String, + example: 'johndoe', + }) username: string; @IsString() @IsNotEmpty() + @ApiProperty({ + description: 'User fullname', + type: String, + example: 'John Doe', + }) fullname: string; @IsStrongPassword() @IsNotEmpty() + @ApiProperty({ + description: 'User password', + type: String, + example: 'P@ssw0rd!', + }) password: string; @IsEnum(AvailableRoles, { - message: `Invalid role. Role should be either ${Roles.STUDENT} or ${Roles.TEACHER}`, + message: `Invalid role. Role should be either ${Role.STUDENT} or ${Role.TEACHER}`, }) @IsNotEmpty() - role: Roles.STUDENT | Roles.TEACHER; + @ApiProperty({ + description: 'User role', + type: String, + example: Role.STUDENT, + enum: [Role.STUDENT, Role.TEACHER], + }) + role: Role.STUDENT | Role.TEACHER; } \ No newline at end of file diff --git a/src/user/dtos/update-user.dto.ts b/src/user/dtos/update-user.dto.ts index efca9b7..f8ad7e7 100644 --- a/src/user/dtos/update-user.dto.ts +++ b/src/user/dtos/update-user.dto.ts @@ -1,11 +1,23 @@ -import { IsString, IsOptional } from "class-validator" +import { IsString, IsOptional, IsStrongPassword } from "class-validator" +import { ApiProperty } from "@nestjs/swagger"; export class UpdateUserDto { @IsString() @IsOptional() + @ApiProperty({ + description: 'User email', + type: String, + example: 'johndoe', + }) username?: string; @IsString() @IsOptional() + @IsStrongPassword() + @ApiProperty({ + description: 'New User Password', + type: String, + example: 'P@ssw0rd!', + }) password?: string; } \ No newline at end of file diff --git a/src/user/dtos/user-response.dto.ts b/src/user/dtos/user-response.dto.ts index 9354455..811ef38 100644 --- a/src/user/dtos/user-response.dto.ts +++ b/src/user/dtos/user-response.dto.ts @@ -1,12 +1,49 @@ -import { Roles } from "src/shared/enums/roles.enum"; +import { Role } from "src/shared/enums/roles.enum"; import { User } from "../user.entity"; +import { ApiProperty } from "@nestjs/swagger"; export class UserResponseDto { + @ApiProperty({ + description: 'User ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) id: string; + + @ApiProperty({ + description: 'User email', + type: String, + example: 'johndoe@gmail.com', + }) email: string; - role: Roles; + + @ApiProperty({ + description: 'User role', + type: String, + example: Role.STUDENT, + enum: [Role.STUDENT, Role.TEACHER], + }) + role: Role; + + @ApiProperty({ + description: 'User created date', + type: Date, + example: new Date(), + }) createdAt: Date; + + @ApiProperty({ + description: 'User updated date', + type: Date, + example: new Date(), + }) updatedAt: Date; + + @ApiProperty({ + description: 'User fullname', + type: String, + example: 'John Doe', + }) fullname: string; constructor(user: User) { diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index ad7693c..6ddc39b 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -6,17 +6,30 @@ import { Param, ParseUUIDPipe, HttpStatus, + Patch, + Body, + Delete, + HttpCode, } from '@nestjs/common'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { UserService } from './user.service'; import { UserResponseDto } from './dtos/user-response.dto'; +import { ApiTags, ApiResponse, ApiBearerAuth, ApiParam, ApiOkResponse } from '@nestjs/swagger'; +import { UpdateUserDto } from './dtos/update-user.dto'; @Controller('user') +@ApiTags('User') +@ApiBearerAuth() @Injectable() export class UserController { constructor(private readonly userService: UserService) { } @Get('profile') + @ApiResponse({ + status: HttpStatus.OK, + type: UserResponseDto, + description: 'Get user profile', + }) async getProfile( @Req() request: AuthenticatedRequest, ): Promise { @@ -27,12 +40,28 @@ export class UserController { } @Get() + @ApiResponse({ + status: HttpStatus.OK, + type: UserResponseDto, + description: 'Get all users', + isArray: true, + }) async findAll(): Promise { const users = await this.userService.findAll(); return users.map((user) => new UserResponseDto(user)); } @Get(':id') + @ApiParam({ + name: 'id', + type: String, + description: 'User id', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: UserResponseDto, + description: 'Get user by id', + }) async findOne( @Param( 'id', @@ -46,4 +75,31 @@ export class UserController { const user = await this.userService.findOne({ where: { id } }); return new UserResponseDto(user); } + + @Patch() + @ApiResponse({ + status: HttpStatus.OK, + type: UserResponseDto, + description: 'Update user', + }) + async update( + @Req() request: AuthenticatedRequest, + @Body() updateUserDto: UpdateUserDto, + ): Promise { + const user = await this.userService.update(request.user.id, updateUserDto); + return new UserResponseDto(user); + } + + @Delete() + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Delete user', + }) + @HttpCode(HttpStatus.NO_CONTENT) + async delete( + @Req() request: AuthenticatedRequest, + ): Promise<{ massage: string }> { + await this.userService.delete(request.user.id); + return { massage: 'User deleted successfully' }; + } } diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index db75286..12674df 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -1,6 +1,6 @@ import { Entity } from "typeorm"; import { Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"; -import { Roles } from "src/shared/enums/roles.enum"; +import { Role } from "src/shared/enums/roles.enum"; @Entity() export class User { @@ -20,10 +20,10 @@ export class User { @Column({ type: "enum", - enum: [Roles.STUDENT, Roles.TEACHER], + enum: [Role.STUDENT, Role.TEACHER], nullable: false, }) - role: Roles; + role: Role; @Column({ nullable: false, diff --git a/src/user/user.service.ts b/src/user/user.service.ts index e36e7ac..ae1fdda 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,9 +1,10 @@ -import { Injectable, Inject, NotFoundException } from "@nestjs/common"; +import { Injectable, Inject, NotFoundException, ForbiddenException } from "@nestjs/common"; import { Repository } from "typeorm"; import { User } from "./user.entity"; import { UpdateUserDto } from "./dtos/update-user.dto"; -import { FindOneOptions, FindOptionsWhere } from "typeorm"; +import { FindOneOptions } from "typeorm"; import { CreateUserDto } from "./dtos/create-user.dto"; +import { hash } from "argon2"; @Injectable() export class UserService { @@ -18,7 +19,7 @@ export class UserService { async findOne(options: FindOneOptions): Promise { const user = this.userRepository.findOne(options); - if (!user) + if (!user) throw new NotFoundException("User not found"); return user; } @@ -29,6 +30,8 @@ export class UserService { async update(id: string, updateUserDto: UpdateUserDto): Promise { try { + if (updateUserDto.password) + updateUserDto.password = await hash(updateUserDto.password); await this.userRepository.update(id, updateUserDto); return await this.findOne({ where: { id } }); } catch (error) { @@ -38,9 +41,9 @@ export class UserService { } } - async delete(options: FindOptionsWhere): Promise { + async delete(id: string): Promise { try { - await this.userRepository.delete(options); + await this.userRepository.delete(id); } catch (error) { if (error instanceof Error) { throw new NotFoundException("User not found"); From d347b4235cd1195e28e38096ce17ad1e8ee08658 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Sat, 9 Nov 2024 21:35:38 +0700 Subject: [PATCH 008/155] chore: Update API documentation title and description --- src/main.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.ts b/src/main.ts index 0917d72..a28d381 100644 --- a/src/main.ts +++ b/src/main.ts @@ -26,10 +26,10 @@ async function bootstrap() { const config = new DocumentBuilder() .addBearerAuth() - .setTitle('Cats example') - .setDescription('The cats API description') + .setTitle('Edusaig API') + .setDescription('This is the Edusaig API documentation') .setVersion('1.0') - .addTag('cats') + .addTag('Edusaig-API') .build(); const documentFactory = () => SwaggerModule.createDocument(app, config); SwaggerModule.setup('docs', app, documentFactory); From 3847f20f138627da96c792285cd2b65f3b33bbf6 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Sat, 9 Nov 2024 21:38:21 +0700 Subject: [PATCH 009/155] chore: Remove unused tag from API documentation configuration --- src/main.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main.ts b/src/main.ts index a28d381..d2301a3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -29,7 +29,6 @@ async function bootstrap() { .setTitle('Edusaig API') .setDescription('This is the Edusaig API documentation') .setVersion('1.0') - .addTag('Edusaig-API') .build(); const documentFactory = () => SwaggerModule.createDocument(app, config); SwaggerModule.setup('docs', app, documentFactory); From 56dc454ad877245981df7bcb077728c1f27d6af6 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Sun, 10 Nov 2024 14:12:53 +0700 Subject: [PATCH 010/155] chore: Update roles enum and related files --- src/shared/enums/roles.enum.ts | 5 +++++ src/user/dtos/create-user.dto.ts | 15 +++++---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/shared/enums/roles.enum.ts b/src/shared/enums/roles.enum.ts index 15dc7ec..8069a17 100644 --- a/src/shared/enums/roles.enum.ts +++ b/src/shared/enums/roles.enum.ts @@ -2,4 +2,9 @@ export enum Role { ADMIN = "admin", STUDENT = "student", TEACHER = "teacher", +} + +export enum AvailableRoles { + STUDENT = Role.STUDENT, + TEACHER = Role.TEACHER, } \ No newline at end of file diff --git a/src/user/dtos/create-user.dto.ts b/src/user/dtos/create-user.dto.ts index be47d18..fa73d75 100644 --- a/src/user/dtos/create-user.dto.ts +++ b/src/user/dtos/create-user.dto.ts @@ -1,12 +1,7 @@ import { IsString, IsEmail, IsStrongPassword, IsEnum, IsNotEmpty } from "class-validator"; -import { Role } from "src/shared/enums/roles.enum"; +import { AvailableRoles } from "src/shared/enums/roles.enum"; import { ApiProperty } from "@nestjs/swagger"; -enum AvailableRoles { - STUDENT = Role.STUDENT, - TEACHER = Role.TEACHER, -} - export class CreateUserDto { @IsEmail() @IsNotEmpty() @@ -45,14 +40,14 @@ export class CreateUserDto { password: string; @IsEnum(AvailableRoles, { - message: `Invalid role. Role should be either ${Role.STUDENT} or ${Role.TEACHER}`, + message: `Invalid role. Role should be either ${AvailableRoles.STUDENT} or ${AvailableRoles.TEACHER}`, }) @IsNotEmpty() @ApiProperty({ description: 'User role', type: String, - example: Role.STUDENT, - enum: [Role.STUDENT, Role.TEACHER], + example: AvailableRoles.STUDENT, + enum: AvailableRoles, }) - role: Role.STUDENT | Role.TEACHER; + role: AvailableRoles; } \ No newline at end of file From dd548542bda003b5fa0fe11cbdf3db42aa6fe644 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Sun, 10 Nov 2024 14:15:10 +0700 Subject: [PATCH 011/155] chore: Update roles enum and related files --- src/user/dtos/create-user.dto.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/user/dtos/create-user.dto.ts b/src/user/dtos/create-user.dto.ts index fa73d75..8957c63 100644 --- a/src/user/dtos/create-user.dto.ts +++ b/src/user/dtos/create-user.dto.ts @@ -1,5 +1,5 @@ import { IsString, IsEmail, IsStrongPassword, IsEnum, IsNotEmpty } from "class-validator"; -import { AvailableRoles } from "src/shared/enums/roles.enum"; +import { AvailableRoles, Role } from "src/shared/enums/roles.enum"; import { ApiProperty } from "@nestjs/swagger"; export class CreateUserDto { @@ -49,5 +49,5 @@ export class CreateUserDto { example: AvailableRoles.STUDENT, enum: AvailableRoles, }) - role: AvailableRoles; + role: Role; } \ No newline at end of file From 927e83be9f53ac401d941863b5d36f287059f921 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Sun, 10 Nov 2024 15:29:06 +0700 Subject: [PATCH 012/155] chore: Add UserStreak entity and module --- src/app.module.ts | 10 +++- src/shared/configs/database.config.ts | 5 +- src/shared/guards/role.guard.ts | 24 ++++----- src/user-streak/user-streak.controller.ts | 52 +++++++++++++++++++ src/user-streak/user-streak.entity.ts | 41 +++++++++++++++ src/user-streak/user-streak.module.ts | 18 +++++++ src/user-streak/user-streak.providers.ts | 8 +++ src/user-streak/user-streak.service.ts | 62 +++++++++++++++++++++++ src/user/user.controller.ts | 5 +- src/user/user.service.ts | 2 +- 10 files changed, 210 insertions(+), 17 deletions(-) create mode 100644 src/user-streak/user-streak.controller.ts create mode 100644 src/user-streak/user-streak.entity.ts create mode 100644 src/user-streak/user-streak.module.ts create mode 100644 src/user-streak/user-streak.providers.ts create mode 100644 src/user-streak/user-streak.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index 818993a..0507d60 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -14,8 +14,11 @@ import { UserModule } from './user/user.module'; import { User } from './user/user.entity'; import { APP_GUARD } from '@nestjs/core'; import { AuthGuard } from './auth/auth.guard'; +import { UserStreak } from './user-streak/user-streak.entity'; +import { UserStreakModule } from './user-streak/user-streak.module'; +import { RolesGuard } from './shared/guards/role.guard'; -const forFeatures = TypeOrmModule.forFeature([User]); +const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); @Module({ imports: [ @@ -40,6 +43,7 @@ const forFeatures = TypeOrmModule.forFeature([User]); }), DatabaseModule, UserModule, + UserStreakModule, ], controllers: [AppController], providers: [ @@ -47,6 +51,10 @@ const forFeatures = TypeOrmModule.forFeature([User]); { provide: APP_GUARD, useClass: AuthGuard, + }, + { + provide: APP_GUARD, + useClass: RolesGuard, } ], }) diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index 2258985..4061057 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -1,8 +1,9 @@ import { DataSourceOptions } from "typeorm"; import { ConfigService } from "@nestjs/config"; import { GLOBAL_CONFIG } from "../constants/global-config.constant"; -import 'dotenv/config'; import { User } from "src/user/user.entity"; +import { UserStreak } from "src/user-streak/user-streak.entity"; +import 'dotenv/config'; const configService = new ConfigService(); @@ -14,5 +15,5 @@ export const databaseConfig: DataSourceOptions = { password: configService.get(GLOBAL_CONFIG.DB_PASSWORD), database: configService.get(GLOBAL_CONFIG.DB_DATABASE), logging: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), - entities: [User], + entities: [User, UserStreak], }; \ No newline at end of file diff --git a/src/shared/guards/role.guard.ts b/src/shared/guards/role.guard.ts index 14b4b8e..336d042 100644 --- a/src/shared/guards/role.guard.ts +++ b/src/shared/guards/role.guard.ts @@ -2,20 +2,20 @@ import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { Role } from '../enums/roles.enum'; import { ROLES_KEY } from '../decorators/role.decorator'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; @Injectable() export class RolesGuard implements CanActivate { - constructor(private reflector: Reflector) {} + constructor(private reflector: Reflector) { } - canActivate(context: ExecutionContext): boolean { - const requiredRoles = this.reflector.getAllAndOverride(ROLES_KEY, [ - context.getHandler(), - context.getClass(), - ]); - if (!requiredRoles) { - return true; + canActivate(context: ExecutionContext): boolean { + const requiredRoles = this.reflector.getAllAndOverride(ROLES_KEY, [ + context.getHandler(), + context.getClass(), + ]); + if (!requiredRoles) + return true; + const { user } = context.switchToHttp().getRequest(); + return requiredRoles.some((role) => role === user.role); } - const { user } = context.switchToHttp().getRequest(); - return requiredRoles.some((role) => role === user.role); - } -} \ No newline at end of file +} diff --git a/src/user-streak/user-streak.controller.ts b/src/user-streak/user-streak.controller.ts new file mode 100644 index 0000000..cf6d0cd --- /dev/null +++ b/src/user-streak/user-streak.controller.ts @@ -0,0 +1,52 @@ +import { Controller, Injectable, Get, Req, Patch, HttpStatus } from "@nestjs/common"; +import { ApiTags, ApiResponse } from "@nestjs/swagger"; +import { UserStreakService } from "./user-streak.service"; +import { UserStreak } from "./user-streak.entity"; +import { AuthenticatedRequest } from "src/auth/interfaces/authenticated-request.interface"; +import { Role } from "src/shared/enums/roles.enum"; +import { Roles } from "src/shared/decorators/role.decorator"; + +@Controller("user-streak") +@ApiTags("User Streak") +@Injectable() +export class UserStreakController { + constructor( + private readonly userStreakService: UserStreakService, + ) { } + + @Get() + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + type: UserStreak, + description: 'Get all user streaks', + isArray: true, + }) + async findAll(): Promise { + return await this.userStreakService.findAll(); + } + + @Get('profile') + @ApiResponse({ + status: HttpStatus.OK, + type: UserStreak, + description: 'Get user streak', + }) + async findOne( + @Req() request: AuthenticatedRequest, + ): Promise { + return await this.userStreakService.findOne({ where: { id: request.user.id } }); + } + + @Patch() + @ApiResponse({ + status: HttpStatus.OK, + type: UserStreak, + description: 'Update user streak', + }) + async update( + @Req() request: AuthenticatedRequest, + ): Promise { + return await this.userStreakService.update(request.user.id); + } +} \ No newline at end of file diff --git a/src/user-streak/user-streak.entity.ts b/src/user-streak/user-streak.entity.ts new file mode 100644 index 0000000..4d19e05 --- /dev/null +++ b/src/user-streak/user-streak.entity.ts @@ -0,0 +1,41 @@ +import { Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"; +import { User } from "src/user/user.entity"; + +@Entity() +export class UserStreak { + @PrimaryGeneratedColumn("uuid") + id: string; + + @OneToOne(() => User) + @JoinColumn() + user: User; + + @Column({ + default: 0, + nullable: false, + }) + currentStreak: number; + + @Column({ + default: 0, + nullable: false, + }) + longestStreak: number; + + @Column({ + type: "timestamp", + }) + lastActivityDate: Date; + + @CreateDateColumn({ + type: "timestamp", + nullable: false, + }) + createdAt: Date; + + @UpdateDateColumn({ + type: "timestamp", + nullable: false, + }) + updatedAt: Date; +} \ No newline at end of file diff --git a/src/user-streak/user-streak.module.ts b/src/user-streak/user-streak.module.ts new file mode 100644 index 0000000..ea0fa1a --- /dev/null +++ b/src/user-streak/user-streak.module.ts @@ -0,0 +1,18 @@ +import { Module } from "@nestjs/common"; +import { UserStreakController } from "./user-streak.controller"; +import { UserStreakService } from "./user-streak.service"; +import { DatabaseModule } from "src/database/database.module"; +import { userStreakProviders } from "./user-streak.providers"; + +@Module({ + imports: [ + DatabaseModule, + ], + controllers: [UserStreakController], + providers: [ + ...userStreakProviders, + UserStreakService + ], + exports: [UserStreakService], +}) +export class UserStreakModule { } \ No newline at end of file diff --git a/src/user-streak/user-streak.providers.ts b/src/user-streak/user-streak.providers.ts new file mode 100644 index 0000000..346220b --- /dev/null +++ b/src/user-streak/user-streak.providers.ts @@ -0,0 +1,8 @@ +import { DataSource } from "typeorm"; +import { UserStreak } from "./user-streak.entity"; + +export const userStreakProviders = [{ + provide: 'UserStreakRepository', + useFactory: (dataSource: DataSource) => dataSource.getRepository(UserStreak), + inject: ['DataSource'], +}]; \ No newline at end of file diff --git a/src/user-streak/user-streak.service.ts b/src/user-streak/user-streak.service.ts new file mode 100644 index 0000000..d88d1ef --- /dev/null +++ b/src/user-streak/user-streak.service.ts @@ -0,0 +1,62 @@ +import { Injectable, Inject, NotFoundException, InternalServerErrorException } from '@nestjs/common'; +import { Repository, FindOneOptions, FindOptionsWhere } from 'typeorm'; +import { UserStreak } from './user-streak.entity'; + +@Injectable() +export class UserStreakService { + constructor( + @Inject('UserStreakRepository') + private readonly userStreakRepository: Repository, + ) { } + + async findAll(): Promise { + return this.userStreakRepository.find(); + } + + async findOne(options: FindOneOptions): Promise { + const userStreak = this.userStreakRepository.findOne(options); + if (!userStreak) throw new NotFoundException('User streak not found'); + return userStreak; + } + + async delete(options: FindOptionsWhere): Promise { + try { + await this.userStreakRepository.delete(options); + } catch (error) { + if (error instanceof Error) { + throw new NotFoundException('User streak not found'); + } + } + } + + async update(userId: string): Promise { + try { + const streak = await this.findOne({ where: { user: { id: userId } } }); + const currentDate = new Date(); + const diff = + Math.abs( + currentDate.getTime() - new Date(streak.lastActivityDate).getTime(), + ) / + (1000 * 60 * 60 * 24); + if (diff > 1) streak.currentStreak = 0; + else streak.currentStreak++; + if (streak.currentStreak > streak.longestStreak) + streak.longestStreak = streak.currentStreak; + await this.userStreakRepository.update( + { + user: { id: userId }, + }, + { + currentStreak: streak.currentStreak, + longestStreak: streak.longestStreak, + lastActivityDate: currentDate, + }, + ); + return await this.findOne({ where: { user: { id: userId } } }); + } catch (error) { + if (error instanceof Error) { + throw new InternalServerErrorException(error.message); + } + } + } +} diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 6ddc39b..e589754 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -14,8 +14,10 @@ import { import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { UserService } from './user.service'; import { UserResponseDto } from './dtos/user-response.dto'; -import { ApiTags, ApiResponse, ApiBearerAuth, ApiParam, ApiOkResponse } from '@nestjs/swagger'; +import { ApiTags, ApiResponse, ApiBearerAuth, ApiParam } from '@nestjs/swagger'; import { UpdateUserDto } from './dtos/update-user.dto'; +import { Role } from 'src/shared/enums/roles.enum'; +import { Roles } from 'src/shared/decorators/role.decorator'; @Controller('user') @ApiTags('User') @@ -40,6 +42,7 @@ export class UserController { } @Get() + @Roles(Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, type: UserResponseDto, diff --git a/src/user/user.service.ts b/src/user/user.service.ts index ae1fdda..bab6122 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Inject, NotFoundException, ForbiddenException } from "@nestjs/common"; +import { Injectable, Inject, NotFoundException } from "@nestjs/common"; import { Repository } from "typeorm"; import { User } from "./user.entity"; import { UpdateUserDto } from "./dtos/update-user.dto"; From ee270317044a123c44d2a7569f5730fbdf7fb549 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Sun, 10 Nov 2024 15:44:01 +0700 Subject: [PATCH 013/155] chore: Add UserStreak module and service, and update related files --- src/auth/auth.module.ts | 2 ++ src/auth/auth.service.ts | 9 +++++++-- src/user-streak/user-streak.controller.ts | 7 ++++--- src/user-streak/user-streak.entity.ts | 10 ++++++++++ src/user-streak/user-streak.service.ts | 3 +-- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 2712cc5..f789649 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -2,10 +2,12 @@ import { Module } from "@nestjs/common"; import { AuthController } from "./auth.controller"; import { AuthService } from "./auth.service"; import { UserModule } from "src/user/user.module"; +import { UserStreakModule } from "src/user-streak/user-streak.module"; @Module({ imports: [ UserModule, + UserStreakModule, ], controllers: [AuthController], providers: [AuthService], diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index aa64282..850e4b6 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -9,6 +9,7 @@ import { AuthResponseDto } from "./dtos/auth-response.dto"; import { JwtPayloadDto } from "./dtos/jwt-payload.dto"; import { GLOBAL_CONFIG } from "src/shared/constants/global-config.constant"; import { UserResponseDto } from "src/user/dtos/user-response.dto"; +import { UserStreakService } from "src/user-streak/user-streak.service"; @Injectable() export class AuthService { @@ -16,6 +17,7 @@ export class AuthService { private readonly configService: ConfigService, private readonly jwtService: JwtService, private readonly userService: UserService, + private readonly userStreakService: UserStreakService, ) { } async login(loginDto: LoginDto): Promise { @@ -26,9 +28,12 @@ export class AuthService { if (!isPasswordValid) throw new BadRequestException("Invalid password"); try { + const accessToken = this.generateAccessToken({ id: user.id, role: user.role }); + const refreshToken = this.generateRefreshToken(); + await this.userStreakService.update(user.id); return { - accessToken: this.generateAccessToken({ id: user.id, role: user.role }), - refreshToken: this.generateRefreshToken(), + accessToken, + refreshToken, user: new UserResponseDto(user), } } catch (error) { diff --git a/src/user-streak/user-streak.controller.ts b/src/user-streak/user-streak.controller.ts index cf6d0cd..8d89450 100644 --- a/src/user-streak/user-streak.controller.ts +++ b/src/user-streak/user-streak.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Injectable, Get, Req, Patch, HttpStatus } from "@nestjs/common"; +import { Controller, Injectable, Get, Req, Patch, HttpStatus, HttpCode } from "@nestjs/common"; import { ApiTags, ApiResponse } from "@nestjs/swagger"; import { UserStreakService } from "./user-streak.service"; import { UserStreak } from "./user-streak.entity"; @@ -40,13 +40,14 @@ export class UserStreakController { @Patch() @ApiResponse({ - status: HttpStatus.OK, + status: HttpStatus.NO_CONTENT, type: UserStreak, description: 'Update user streak', }) + @HttpCode(HttpStatus.NO_CONTENT) async update( @Req() request: AuthenticatedRequest, - ): Promise { + ): Promise { return await this.userStreakService.update(request.user.id); } } \ No newline at end of file diff --git a/src/user-streak/user-streak.entity.ts b/src/user-streak/user-streak.entity.ts index 4d19e05..3c27b05 100644 --- a/src/user-streak/user-streak.entity.ts +++ b/src/user-streak/user-streak.entity.ts @@ -1,13 +1,23 @@ import { Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"; import { User } from "src/user/user.entity"; +import { ApiProperty } from "@nestjs/swagger"; @Entity() export class UserStreak { @PrimaryGeneratedColumn("uuid") + @ApiProperty({ + description: 'User streak ID', + type: String, + example: '123e4567-e89b-12d3-a456-426614174000', + }) id: string; @OneToOne(() => User) @JoinColumn() + @ApiProperty({ + description: 'User', + type: User, + }) user: User; @Column({ diff --git a/src/user-streak/user-streak.service.ts b/src/user-streak/user-streak.service.ts index d88d1ef..4dbc4fb 100644 --- a/src/user-streak/user-streak.service.ts +++ b/src/user-streak/user-streak.service.ts @@ -29,7 +29,7 @@ export class UserStreakService { } } - async update(userId: string): Promise { + async update(userId: string): Promise { try { const streak = await this.findOne({ where: { user: { id: userId } } }); const currentDate = new Date(); @@ -52,7 +52,6 @@ export class UserStreakService { lastActivityDate: currentDate, }, ); - return await this.findOne({ where: { user: { id: userId } } }); } catch (error) { if (error instanceof Error) { throw new InternalServerErrorException(error.message); From 2b64d3cc30eddc1eb5574c418a8e073c78f7ef21 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Mon, 11 Nov 2024 00:23:14 +0700 Subject: [PATCH 014/155] chore: Update Dockerfile and docker-compose.yml for database migrations --- .gitignore | 3 ++- Dockerfile | 1 + docker-compose.yml | 2 ++ package.json | 5 +++++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3bb7fb8..234e8eb 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,5 @@ pnpm-lock.yaml .data /files .env -/ormconfig.json \ No newline at end of file +/ormconfig.json +/src/database/migrations \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index b27f0cc..f5ac050 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,7 @@ FROM base AS deploy RUN apk add --update curl && rm -rf /var/cache/apk/* WORKDIR /app +COPY --from=build --chown=node:node /app/package.json /app/pnpm-lock.yaml ./ COPY --from=build --chown=node:node /app/dist ./dist COPY --from=build --chown=node:node /app/node_modules ./node_modules COPY --chown=node:node .env .env diff --git a/docker-compose.yml b/docker-compose.yml index b945076..72ca334 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,3 +34,5 @@ services: - TZ=Asia/Bangkok ports: - '${PORT}:${PORT}' + volumes: + - ./src/database/migrations:/app/dist/database/migrations diff --git a/package.json b/package.json index dbc8a66..a53a3f5 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,11 @@ "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", + "migration:generate": "pnpx typeorm migration:generate -o -d dist/shared/configs/database.config.js dist/database/migrations/migration", + "migration:create": "pnpx typeorm migration:create dist/database/migrations/migration", + "migration:run": "pnpx typeorm migration:run -- -d dist/shared/configs/database.config.js", + "migration:revert": "pnpm run typeorm -- migration:revert", + "migration:generate:prod": "docker compose run app pnpm migration:generate", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { From 3aa508396f881812f0dd381d78c38a715fbc611c Mon Sep 17 00:00:00 2001 From: ganthepro Date: Mon, 11 Nov 2024 00:24:38 +0700 Subject: [PATCH 015/155] chore: Update database configuration and add DataSource export --- src/shared/configs/database.config.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index 4061057..e9a41a0 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -1,9 +1,9 @@ -import { DataSourceOptions } from "typeorm"; +import { DataSourceOptions, DataSource } from "typeorm"; import { ConfigService } from "@nestjs/config"; import { GLOBAL_CONFIG } from "../constants/global-config.constant"; +import 'dotenv/config'; import { User } from "src/user/user.entity"; import { UserStreak } from "src/user-streak/user-streak.entity"; -import 'dotenv/config'; const configService = new ConfigService(); @@ -16,4 +16,6 @@ export const databaseConfig: DataSourceOptions = { database: configService.get(GLOBAL_CONFIG.DB_DATABASE), logging: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), entities: [User, UserStreak], -}; \ No newline at end of file +}; + +export default new DataSource(databaseConfig); \ No newline at end of file From 6f19dc857ac0d6a8b4b740869022e4e9c0250cdf Mon Sep 17 00:00:00 2001 From: ganthepro Date: Mon, 11 Nov 2024 00:33:50 +0700 Subject: [PATCH 016/155] chore: Update UserStreak module and service, and related files --- src/auth/auth.controller.ts | 5 ++++- src/auth/auth.service.ts | 7 +++++-- src/user-streak/user-streak.controller.ts | 3 ++- src/user-streak/user-streak.entity.ts | 7 ++++--- src/user-streak/user-streak.service.ts | 6 ++++++ src/user/dtos/create-user.dto.ts | 2 +- src/user/user.controller.ts | 2 ++ src/user/user.entity.ts | 6 +++--- src/user/user.service.ts | 15 +++++++++------ 9 files changed, 36 insertions(+), 17 deletions(-) diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index b888ba1..367c9a0 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -10,7 +10,9 @@ import { AuthResponseDto } from './dtos/auth-response.dto'; @ApiTags('Auth') @Injectable() export class AuthController { - constructor(private readonly authService: AuthService) { } + constructor( + private readonly authService: AuthService, + ) { } @Post('login') @ApiResponse({ @@ -29,6 +31,7 @@ export class AuthController { description: 'Register', type: AuthResponseDto }) + @HttpCode(HttpStatus.CREATED) @Public() async register(@Body() registerDto: RegisterDto): Promise { return await this.authService.register(registerDto); diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 850e4b6..ddec553 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -49,9 +49,12 @@ export class AuthService { const hashedPassword = await hash(registerDto.password); const createdUser = await this.userService.create({ ...registerDto, password: hashedPassword }); try { + const accessToken = this.generateAccessToken({ id: createdUser.id, role: createdUser.role }); + const refreshToken = this.generateRefreshToken(); + await this.userStreakService.create(createdUser.id); return { - accessToken: this.generateAccessToken({ id: createdUser.id, role: createdUser.role }), - refreshToken: this.generateRefreshToken(), + accessToken, + refreshToken, user: new UserResponseDto(createdUser), } } catch (error) { diff --git a/src/user-streak/user-streak.controller.ts b/src/user-streak/user-streak.controller.ts index 8d89450..88ac0ab 100644 --- a/src/user-streak/user-streak.controller.ts +++ b/src/user-streak/user-streak.controller.ts @@ -1,5 +1,5 @@ import { Controller, Injectable, Get, Req, Patch, HttpStatus, HttpCode } from "@nestjs/common"; -import { ApiTags, ApiResponse } from "@nestjs/swagger"; +import { ApiTags, ApiResponse, ApiBearerAuth } from "@nestjs/swagger"; import { UserStreakService } from "./user-streak.service"; import { UserStreak } from "./user-streak.entity"; import { AuthenticatedRequest } from "src/auth/interfaces/authenticated-request.interface"; @@ -8,6 +8,7 @@ import { Roles } from "src/shared/decorators/role.decorator"; @Controller("user-streak") @ApiTags("User Streak") +@ApiBearerAuth() @Injectable() export class UserStreakController { constructor( diff --git a/src/user-streak/user-streak.entity.ts b/src/user-streak/user-streak.entity.ts index 3c27b05..da6aa6a 100644 --- a/src/user-streak/user-streak.entity.ts +++ b/src/user-streak/user-streak.entity.ts @@ -33,18 +33,19 @@ export class UserStreak { longestStreak: number; @Column({ - type: "timestamp", + type: "timestamp with time zone", + default: () => "CURRENT_TIMESTAMP", }) lastActivityDate: Date; @CreateDateColumn({ - type: "timestamp", + type: "timestamp with time zone", nullable: false, }) createdAt: Date; @UpdateDateColumn({ - type: "timestamp", + type: "timestamp with time zone", nullable: false, }) updatedAt: Date; diff --git a/src/user-streak/user-streak.service.ts b/src/user-streak/user-streak.service.ts index 4dbc4fb..dba77ec 100644 --- a/src/user-streak/user-streak.service.ts +++ b/src/user-streak/user-streak.service.ts @@ -9,6 +9,12 @@ export class UserStreakService { private readonly userStreakRepository: Repository, ) { } + async create(userId: string): Promise { + return await this.userStreakRepository.save({ + user: { id: userId }, + }) + } + async findAll(): Promise { return this.userStreakRepository.find(); } diff --git a/src/user/dtos/create-user.dto.ts b/src/user/dtos/create-user.dto.ts index 8957c63..0590146 100644 --- a/src/user/dtos/create-user.dto.ts +++ b/src/user/dtos/create-user.dto.ts @@ -49,5 +49,5 @@ export class CreateUserDto { example: AvailableRoles.STUDENT, enum: AvailableRoles, }) - role: Role; + role: Role.STUDENT | Role.TEACHER; } \ No newline at end of file diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index e589754..21551d8 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -18,6 +18,7 @@ import { ApiTags, ApiResponse, ApiBearerAuth, ApiParam } from '@nestjs/swagger'; import { UpdateUserDto } from './dtos/update-user.dto'; import { Role } from 'src/shared/enums/roles.enum'; import { Roles } from 'src/shared/decorators/role.decorator'; +import { Public } from 'src/shared/decorators/public.decorator'; @Controller('user') @ApiTags('User') @@ -65,6 +66,7 @@ export class UserController { type: UserResponseDto, description: 'Get user by id', }) + @Public() async findOne( @Param( 'id', diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index 12674df..c074118 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -20,7 +20,7 @@ export class User { @Column({ type: "enum", - enum: [Role.STUDENT, Role.TEACHER], + enum: Role, nullable: false, }) role: Role; @@ -38,13 +38,13 @@ export class User { email: string; @CreateDateColumn({ - type: "timestamp", + type: "timestamp with time zone", nullable: false, }) createdAt: Date; @UpdateDateColumn({ - type: "timestamp", + type: "timestamp with time zone", nullable: false, }) updatedAt: Date; diff --git a/src/user/user.service.ts b/src/user/user.service.ts index bab6122..e720f39 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,4 +1,4 @@ -import { Injectable, Inject, NotFoundException } from "@nestjs/common"; +import { Injectable, Inject, NotFoundException, BadRequestException } from "@nestjs/common"; import { Repository } from "typeorm"; import { User } from "./user.entity"; import { UpdateUserDto } from "./dtos/update-user.dto"; @@ -25,7 +25,12 @@ export class UserService { } async create(createUserDto: CreateUserDto): Promise { - return this.userRepository.save(createUserDto); + try { + return this.userRepository.save(createUserDto); + } catch (error) { + if (error instanceof Error) + throw new BadRequestException(error.message); + } } async update(id: string, updateUserDto: UpdateUserDto): Promise { @@ -35,9 +40,8 @@ export class UserService { await this.userRepository.update(id, updateUserDto); return await this.findOne({ where: { id } }); } catch (error) { - if (error instanceof Error) { + if (error instanceof Error) throw new NotFoundException("User not found"); - } } } @@ -45,9 +49,8 @@ export class UserService { try { await this.userRepository.delete(id); } catch (error) { - if (error instanceof Error) { + if (error instanceof Error) throw new NotFoundException("User not found"); - } } } } \ No newline at end of file From 73675c7e4e4de4fc4e57f998d7f35089e889b00a Mon Sep 17 00:00:00 2001 From: ganthepro Date: Mon, 11 Nov 2024 00:57:18 +0700 Subject: [PATCH 017/155] chore: Refactor UserStreakController to use UserStreakResponseDto --- .../dtos/user-streak-response.dto.ts | 63 +++++++++++++++++++ src/user-streak/user-streak.controller.ts | 55 +++++++++------- src/user-streak/user-streak.entity.ts | 10 --- src/user-streak/user-streak.service.ts | 5 +- 4 files changed, 99 insertions(+), 34 deletions(-) create mode 100644 src/user-streak/dtos/user-streak-response.dto.ts diff --git a/src/user-streak/dtos/user-streak-response.dto.ts b/src/user-streak/dtos/user-streak-response.dto.ts new file mode 100644 index 0000000..b2facf1 --- /dev/null +++ b/src/user-streak/dtos/user-streak-response.dto.ts @@ -0,0 +1,63 @@ +import { ApiProperty } from "@nestjs/swagger" +import { UserStreak } from "../user-streak.entity"; + +export class UserStreakResponseDto { + @ApiProperty({ + description: 'User streak ID', + type: String, + example: '123e4567-e89b-12d3-a456-426614174000', + }) + id: string; + + @ApiProperty({ + description: 'User current streak', + type: Number, + example: 0, + }) + currentStreak: number; + + @ApiProperty({ + description: 'User longest streak', + type: Number, + example: 0, + }) + longestStreak: number; + + @ApiProperty({ + description: 'User last activity date', + type: Date, + example: '2021-08-01T00:00:00.000Z', + }) + lastActivityDate: Date; + + @ApiProperty({ + description: 'User created date', + type: Date, + example: '2021-08-01T00:00:00.000Z', + }) + createdAt: Date; + + @ApiProperty({ + description: 'User updated date', + type: Date, + example: '2021-08-01T00:00:00.000Z', + }) + updatedAt: Date; + + @ApiProperty({ + description: 'User ID', + type: String, + example: '123e4567-e89b-12d3-a456-426614174000', + }) + userId: string; + + constructor(userStreak: UserStreak) { + this.id = userStreak.id; + this.currentStreak = userStreak.currentStreak; + this.longestStreak = userStreak.longestStreak; + this.lastActivityDate = userStreak.lastActivityDate; + this.createdAt = userStreak.createdAt; + this.updatedAt = userStreak.updatedAt; + this.userId = userStreak.user.id; + } +} \ No newline at end of file diff --git a/src/user-streak/user-streak.controller.ts b/src/user-streak/user-streak.controller.ts index 88ac0ab..7bb6dd9 100644 --- a/src/user-streak/user-streak.controller.ts +++ b/src/user-streak/user-streak.controller.ts @@ -1,19 +1,26 @@ -import { Controller, Injectable, Get, Req, Patch, HttpStatus, HttpCode } from "@nestjs/common"; -import { ApiTags, ApiResponse, ApiBearerAuth } from "@nestjs/swagger"; -import { UserStreakService } from "./user-streak.service"; -import { UserStreak } from "./user-streak.entity"; -import { AuthenticatedRequest } from "src/auth/interfaces/authenticated-request.interface"; -import { Role } from "src/shared/enums/roles.enum"; -import { Roles } from "src/shared/decorators/role.decorator"; +import { + Controller, + Injectable, + Get, + Req, + Patch, + HttpStatus, + HttpCode, +} from '@nestjs/common'; +import { ApiTags, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; +import { UserStreakService } from './user-streak.service'; +import { UserStreak } from './user-streak.entity'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { Role } from 'src/shared/enums/roles.enum'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { UserStreakResponseDto } from './dtos/user-streak-response.dto'; -@Controller("user-streak") -@ApiTags("User Streak") +@Controller('user-streak') +@ApiTags('User Streak') @ApiBearerAuth() @Injectable() export class UserStreakController { - constructor( - private readonly userStreakService: UserStreakService, - ) { } + constructor(private readonly userStreakService: UserStreakService) { } @Get() @Roles(Role.ADMIN) @@ -23,8 +30,11 @@ export class UserStreakController { description: 'Get all user streaks', isArray: true, }) - async findAll(): Promise { - return await this.userStreakService.findAll(); + async findAll(): Promise { + const userStreaks = await this.userStreakService.findAll(); + return userStreaks.map( + (userStreak) => new UserStreakResponseDto(userStreak), + ); } @Get('profile') @@ -35,20 +45,21 @@ export class UserStreakController { }) async findOne( @Req() request: AuthenticatedRequest, - ): Promise { - return await this.userStreakService.findOne({ where: { id: request.user.id } }); + ): Promise { + const userStreak = await this.userStreakService.findOne({ + where: { user: { id: request.user.id } }, + relations: { user: true }, + }); + return new UserStreakResponseDto(userStreak); } - @Patch() + @Patch() @ApiResponse({ status: HttpStatus.NO_CONTENT, - type: UserStreak, description: 'Update user streak', }) @HttpCode(HttpStatus.NO_CONTENT) - async update( - @Req() request: AuthenticatedRequest, - ): Promise { + async update(@Req() request: AuthenticatedRequest): Promise { return await this.userStreakService.update(request.user.id); } -} \ No newline at end of file +} diff --git a/src/user-streak/user-streak.entity.ts b/src/user-streak/user-streak.entity.ts index da6aa6a..74897fd 100644 --- a/src/user-streak/user-streak.entity.ts +++ b/src/user-streak/user-streak.entity.ts @@ -1,23 +1,13 @@ import { Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"; import { User } from "src/user/user.entity"; -import { ApiProperty } from "@nestjs/swagger"; @Entity() export class UserStreak { @PrimaryGeneratedColumn("uuid") - @ApiProperty({ - description: 'User streak ID', - type: String, - example: '123e4567-e89b-12d3-a456-426614174000', - }) id: string; @OneToOne(() => User) @JoinColumn() - @ApiProperty({ - description: 'User', - type: User, - }) user: User; @Column({ diff --git a/src/user-streak/user-streak.service.ts b/src/user-streak/user-streak.service.ts index dba77ec..a07697f 100644 --- a/src/user-streak/user-streak.service.ts +++ b/src/user-streak/user-streak.service.ts @@ -20,8 +20,9 @@ export class UserStreakService { } async findOne(options: FindOneOptions): Promise { - const userStreak = this.userStreakRepository.findOne(options); - if (!userStreak) throw new NotFoundException('User streak not found'); + const userStreak = await this.userStreakRepository.findOne(options); + if (!userStreak) + throw new NotFoundException('User streak not found'); return userStreak; } From 142f26d2ede65b524d582b92674e1eaff3a01c4f Mon Sep 17 00:00:00 2001 From: Potsawee Date: Mon, 11 Nov 2024 14:28:42 +0700 Subject: [PATCH 018/155] add CRUD for category --- src/app.module.ts | 66 +++++++++--------- src/category/category.controller.ts | 79 ++++++++++++++++++++++ src/category/category.entity.ts | 39 +++++++++++ src/category/category.module.ts | 12 ++++ src/category/category.providers.ts | 10 +++ src/category/category.service.ts | 61 +++++++++++++++++ src/category/dtos/category-response.dto.ts | 19 ++++++ src/category/dtos/create-cateory.dto.ts | 15 ++++ src/category/dtos/update-category.dto.ts | 15 ++++ src/shared/configs/database.config.ts | 27 ++++---- 10 files changed, 298 insertions(+), 45 deletions(-) create mode 100644 src/category/category.controller.ts create mode 100644 src/category/category.entity.ts create mode 100644 src/category/category.module.ts create mode 100644 src/category/category.providers.ts create mode 100644 src/category/category.service.ts create mode 100644 src/category/dtos/category-response.dto.ts create mode 100644 src/category/dtos/create-cateory.dto.ts create mode 100644 src/category/dtos/update-category.dto.ts diff --git a/src/app.module.ts b/src/app.module.ts index 818993a..0a3cc98 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -14,40 +14,42 @@ import { UserModule } from './user/user.module'; import { User } from './user/user.entity'; import { APP_GUARD } from '@nestjs/core'; import { AuthGuard } from './auth/auth.guard'; +import { CategoryModule } from './category/category.module'; const forFeatures = TypeOrmModule.forFeature([User]); @Module({ - imports: [ - forFeatures, - AuthModule, - ConfigModule.forRoot({ - isGlobal: true, - validationSchema: dotenvConfig, - }), - TypeOrmModule.forRootAsync({ - imports: [ConfigModule], - useFactory: (configService: ConfigService) => ({ - ...databaseConfig, - migrations: ["dist/database/migrations/*.js"], - migrationsRun: true, - synchronize: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), - }), - inject: [ConfigService], - }), - JwtModule.register({ - global: true, - }), - DatabaseModule, - UserModule, - ], - controllers: [AppController], - providers: [ - AppService, - { - provide: APP_GUARD, - useClass: AuthGuard, - } - ], + imports: [ + forFeatures, + AuthModule, + ConfigModule.forRoot({ + isGlobal: true, + validationSchema: dotenvConfig, + }), + TypeOrmModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + ...databaseConfig, + migrations: ['dist/database/migrations/*.js'], + migrationsRun: true, + synchronize: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), + }), + inject: [ConfigService], + }), + JwtModule.register({ + global: true, + }), + DatabaseModule, + UserModule, + CategoryModule, + ], + controllers: [AppController], + providers: [ + AppService, + { + provide: APP_GUARD, + useClass: AuthGuard, + }, + ], }) -export class AppModule { } +export class AppModule {} diff --git a/src/category/category.controller.ts b/src/category/category.controller.ts new file mode 100644 index 0000000..14d367f --- /dev/null +++ b/src/category/category.controller.ts @@ -0,0 +1,79 @@ +import { + Body, + Controller, + Delete, + HttpStatus, + Param, + ParseUUIDPipe, + Patch, + Post, +} from '@nestjs/common'; +import { CategoryService } from './category.service'; +import { categoryResponseDto } from './dtos/category-response.dto'; +import { Get } from '@nestjs/common'; +import { CreateCategoryDto } from './dtos/create-cateory.dto'; +import { updateCategoryDto } from './dtos/update-category.dto'; +import { Public } from 'src/shared/decorators/public.decorator'; + +@Controller('category') +export class CategoryController { + constructor(private readonly categoryService: CategoryService) {} + + @Post() + async create( + @Body() CreateCategoryDto: CreateCategoryDto, + ): Promise { + return this.categoryService.create(CreateCategoryDto); + } + + @Get() + @Public() + async findAll(): Promise { + const categories = await this.categoryService.findAll(); + return categories.map((category) => new categoryResponseDto(category)); + } + + @Get(':id') + async findOne( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const category = await this.categoryService.findOne({ where: { id } }); + return new categoryResponseDto(category); + } + + @Patch(':id') + async update( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @Body() updateCategoryDto: updateCategoryDto, + ): Promise { + return this.categoryService.update(id, updateCategoryDto); + } + + @Delete(':id') + async delete( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + return this.categoryService.delete({ id }); + } +} diff --git a/src/category/category.entity.ts b/src/category/category.entity.ts new file mode 100644 index 0000000..c0deb5a --- /dev/null +++ b/src/category/category.entity.ts @@ -0,0 +1,39 @@ +import { Entity } from 'typeorm'; +import { + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, +} from 'typeorm'; + +@Entity() +export class Category { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ + nullable: false, + unique: true, + }) + title: string; + + @Column() + description: string; + + @Column({ + nullable: false, + }) + slug: string; + + @CreateDateColumn({ + type: 'timestamp', + nullable: false, + }) + createdAt: Date; + + @UpdateDateColumn({ + type: 'timestamp', + nullable: false, + }) + updatedAt: Date; +} diff --git a/src/category/category.module.ts b/src/category/category.module.ts new file mode 100644 index 0000000..7a3f409 --- /dev/null +++ b/src/category/category.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { DatabaseModule } from 'src/database/database.module'; +import { CategoryController } from './category.controller'; +import { CategoryService } from './category.service'; +import { categoryProviders } from './category.providers'; + +@Module({ + imports: [DatabaseModule], + controllers: [CategoryController], + providers: [...categoryProviders, CategoryService], +}) +export class CategoryModule {} diff --git a/src/category/category.providers.ts b/src/category/category.providers.ts new file mode 100644 index 0000000..ab28dd7 --- /dev/null +++ b/src/category/category.providers.ts @@ -0,0 +1,10 @@ +import { DataSource } from 'typeorm'; +import { Category } from './category.entity'; + +export const categoryProviders = [ + { + provide: 'CategoryRepository', + useFactory: (DataSource: DataSource) => DataSource.getRepository(Category), + inject: ['DataSource'], + }, +]; diff --git a/src/category/category.service.ts b/src/category/category.service.ts new file mode 100644 index 0000000..65e7704 --- /dev/null +++ b/src/category/category.service.ts @@ -0,0 +1,61 @@ +import { + Inject, + Injectable, + NotFoundException, + BadRequestException, +} from '@nestjs/common'; +import { CreateCategoryDto } from './dtos/create-cateory.dto'; +import { Category } from './category.entity'; +import { FindOneOptions, FindOptionsWhere, Repository } from 'typeorm'; +import { updateCategoryDto } from './dtos/update-category.dto'; + +@Injectable() +export class CategoryService { + constructor( + @Inject('CategoryRepository') + private readonly categoryRepository: Repository, + ) {} + + async findAll(): Promise { + return this.categoryRepository.find(); + } + + async findOne(options: FindOneOptions): Promise { + const category = await this.categoryRepository.findOne(options); + if (!category) throw new NotFoundException('Category not found'); + return category; + } + + async create(CreateCategoryDto: CreateCategoryDto): Promise { + try { + const category = await this.categoryRepository.save(CreateCategoryDto); + return category; + } catch (error) { + if (error instanceof Error) + throw new BadRequestException('category already exists'); + } + } + + async update( + id: string, + updateCategoryDto: updateCategoryDto, + ): Promise { + try { + await this.categoryRepository.update(id, updateCategoryDto); + return this.categoryRepository.findOne({ where: { id } }); + } catch (error) { + if (error instanceof Error) { + throw new NotFoundException('category not found'); + } + } + } + + async delete(options: FindOptionsWhere): Promise { + try { + await this.categoryRepository.delete(options); + } catch (error) { + if (error instanceof Error) + throw new NotFoundException('category not found'); + } + } +} diff --git a/src/category/dtos/category-response.dto.ts b/src/category/dtos/category-response.dto.ts new file mode 100644 index 0000000..e5a2a99 --- /dev/null +++ b/src/category/dtos/category-response.dto.ts @@ -0,0 +1,19 @@ +import { Category } from '../category.entity'; + +export class categoryResponseDto { + id: string; + title: string; + description: string; + slug: string; + createdAt: Date; + updatedAt: Date; + + constructor(category: Category) { + this.id = category.id; + this.title = category.title; + this.description = category.description; + this.slug = category.slug; + this.createdAt = category.createdAt; + this.updatedAt = category.updatedAt; + } +} diff --git a/src/category/dtos/create-cateory.dto.ts b/src/category/dtos/create-cateory.dto.ts new file mode 100644 index 0000000..6bfc13c --- /dev/null +++ b/src/category/dtos/create-cateory.dto.ts @@ -0,0 +1,15 @@ +import { IsNotEmpty, IsOptional, IsString } from '@nestjs/class-validator'; + +export class CreateCategoryDto { + @IsNotEmpty() + @IsString() + title: string; + + @IsOptional() + @IsString() + description?: string; + + @IsString() + @IsNotEmpty() + slug: string; +} diff --git a/src/category/dtos/update-category.dto.ts b/src/category/dtos/update-category.dto.ts new file mode 100644 index 0000000..11e2116 --- /dev/null +++ b/src/category/dtos/update-category.dto.ts @@ -0,0 +1,15 @@ +import { IsOptional, IsString } from '@nestjs/class-validator'; + +export class updateCategoryDto { + @IsOptional() + @IsString() + title?: string; + + @IsOptional() + @IsString() + description?: string; + + @IsOptional() + @IsString() + slug?: string; +} diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index 2258985..4893d29 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -1,18 +1,19 @@ -import { DataSourceOptions } from "typeorm"; -import { ConfigService } from "@nestjs/config"; -import { GLOBAL_CONFIG } from "../constants/global-config.constant"; +import { DataSourceOptions } from 'typeorm'; +import { ConfigService } from '@nestjs/config'; +import { GLOBAL_CONFIG } from '../constants/global-config.constant'; import 'dotenv/config'; -import { User } from "src/user/user.entity"; +import { User } from 'src/user/user.entity'; +import { Category } from 'src/category/category.entity'; const configService = new ConfigService(); export const databaseConfig: DataSourceOptions = { - type: "postgres", - host: configService.get(GLOBAL_CONFIG.DB_HOST), - port: configService.get(GLOBAL_CONFIG.DB_PORT), - username: configService.get(GLOBAL_CONFIG.DB_USERNAME), - password: configService.get(GLOBAL_CONFIG.DB_PASSWORD), - database: configService.get(GLOBAL_CONFIG.DB_DATABASE), - logging: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), - entities: [User], -}; \ No newline at end of file + type: 'postgres', + host: configService.get(GLOBAL_CONFIG.DB_HOST), + port: configService.get(GLOBAL_CONFIG.DB_PORT), + username: configService.get(GLOBAL_CONFIG.DB_USERNAME), + password: configService.get(GLOBAL_CONFIG.DB_PASSWORD), + database: configService.get(GLOBAL_CONFIG.DB_DATABASE), + logging: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), + entities: [User, Category], +}; From 6d11afdd4b6eb3917e72e050ccea5516a7c9e2ac Mon Sep 17 00:00:00 2001 From: Potsawee Date: Mon, 11 Nov 2024 22:02:17 +0700 Subject: [PATCH 019/155] add enum slug --- src/category/category.entity.ts | 5 ++++- src/category/dtos/category-response.dto.ts | 3 ++- src/category/dtos/create-cateory.dto.ts | 19 ++++++++++++++----- src/category/dtos/update-category.dto.ts | 14 +++++++++----- src/shared/enums/slug.enum.ts | 4 ++++ 5 files changed, 33 insertions(+), 12 deletions(-) create mode 100644 src/shared/enums/slug.enum.ts diff --git a/src/category/category.entity.ts b/src/category/category.entity.ts index c0deb5a..f2521b5 100644 --- a/src/category/category.entity.ts +++ b/src/category/category.entity.ts @@ -1,3 +1,4 @@ +import { Slug } from 'src/shared/enums/slug.enum'; import { Entity } from 'typeorm'; import { Column, @@ -22,8 +23,10 @@ export class Category { @Column({ nullable: false, + type: 'enum', + enum: Slug, }) - slug: string; + slug: Slug; @CreateDateColumn({ type: 'timestamp', diff --git a/src/category/dtos/category-response.dto.ts b/src/category/dtos/category-response.dto.ts index e5a2a99..cf36ef3 100644 --- a/src/category/dtos/category-response.dto.ts +++ b/src/category/dtos/category-response.dto.ts @@ -1,10 +1,11 @@ +import { Slug } from 'src/shared/enums/slug.enum'; import { Category } from '../category.entity'; export class categoryResponseDto { id: string; title: string; description: string; - slug: string; + slug: Slug; createdAt: Date; updatedAt: Date; diff --git a/src/category/dtos/create-cateory.dto.ts b/src/category/dtos/create-cateory.dto.ts index b000217..928910e 100644 --- a/src/category/dtos/create-cateory.dto.ts +++ b/src/category/dtos/create-cateory.dto.ts @@ -1,5 +1,11 @@ -import { IsNotEmpty, IsOptional, IsString } from '@nestjs/class-validator'; +import { + IsEnum, + IsNotEmpty, + IsOptional, + IsString, +} from '@nestjs/class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Slug } from 'src/shared/enums/slug.enum'; export class CreateCategoryDto { @IsNotEmpty() @@ -20,12 +26,15 @@ export class CreateCategoryDto { }) description?: string; - @IsString() + @IsEnum(Slug, { + message: `Invalid role. Role should be either ${Slug.COURSE} or ${Slug.REWARD}`, + }) @IsNotEmpty() @ApiProperty({ - description: '-', + description: 'slug', type: String, - example: '-', + example: Slug.COURSE, + enum: Slug, }) - slug: string; + slug: Slug; } diff --git a/src/category/dtos/update-category.dto.ts b/src/category/dtos/update-category.dto.ts index dd3cac2..b2494b6 100644 --- a/src/category/dtos/update-category.dto.ts +++ b/src/category/dtos/update-category.dto.ts @@ -1,5 +1,6 @@ -import { IsOptional, IsString } from '@nestjs/class-validator'; +import { IsOptional, IsString, IsEnum } from '@nestjs/class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Slug } from 'src/shared/enums/slug.enum'; export class updateCategoryDto { @IsOptional() @@ -20,12 +21,15 @@ export class updateCategoryDto { }) description?: string; + @IsEnum(Slug, { + message: `Invalid role. Role should be either ${Slug.COURSE} or ${Slug.REWARD}`, + }) @IsOptional() - @IsString() @ApiProperty({ - description: '-', + description: 'slug', type: String, - example: '-', + example: Slug.COURSE, + enum: Slug, }) - slug?: string; + slug: Slug; } diff --git a/src/shared/enums/slug.enum.ts b/src/shared/enums/slug.enum.ts new file mode 100644 index 0000000..c65dfb1 --- /dev/null +++ b/src/shared/enums/slug.enum.ts @@ -0,0 +1,4 @@ +export enum Slug { + COURSE = 'course', + REWARD = 'reward', +} From b02beac716e13ccdd3baa6812adb94fcc60b6c37 Mon Sep 17 00:00:00 2001 From: Potsawee Date: Tue, 12 Nov 2024 13:38:53 +0700 Subject: [PATCH 020/155] create reward and user reward directory --- src/reward/dtos/create-reward.dto.ts | 34 +++++++++++++++++++ src/reward/reward.controllers.ts | 0 src/reward/reward.entity.ts | 42 ++++++++++++++++++++++++ src/reward/reward.module.ts | 0 src/reward/reward.service.ts | 0 src/userReward/userReward.controllers.ts | 0 src/userReward/userReward.module.ts | 0 src/userReward/userReward.service.ts | 0 8 files changed, 76 insertions(+) create mode 100644 src/reward/dtos/create-reward.dto.ts create mode 100644 src/reward/reward.controllers.ts create mode 100644 src/reward/reward.entity.ts create mode 100644 src/reward/reward.module.ts create mode 100644 src/reward/reward.service.ts create mode 100644 src/userReward/userReward.controllers.ts create mode 100644 src/userReward/userReward.module.ts create mode 100644 src/userReward/userReward.service.ts diff --git a/src/reward/dtos/create-reward.dto.ts b/src/reward/dtos/create-reward.dto.ts new file mode 100644 index 0000000..0c1d4e5 --- /dev/null +++ b/src/reward/dtos/create-reward.dto.ts @@ -0,0 +1,34 @@ +import { + IsNotEmpty, + IsNumber, + IsOptional, + IsString, +} from '@nestjs/class-validator'; + +export class CreateRewardDto { + @IsString() + @IsNotEmpty() + name: string; + + @IsOptional() + @IsString() + description?: string; + + @IsString() + thumnail: string; + + @IsString() + type: string; + + @IsNumber() + @IsNotEmpty() + points: number; + + @IsNumber() + @IsNotEmpty() + stock: number; + + @IsString() + @IsNotEmpty() + status: string; +} diff --git a/src/reward/reward.controllers.ts b/src/reward/reward.controllers.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/reward/reward.entity.ts b/src/reward/reward.entity.ts new file mode 100644 index 0000000..aa7bae0 --- /dev/null +++ b/src/reward/reward.entity.ts @@ -0,0 +1,42 @@ +import { + Column, + CreateDateColumn, + Entity, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +@Entity() +export class Reward { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ + nullable: false, + }) + name: string; + + @Column({}) + description: string; + + @Column() + thumbnail: string; + + @Column() + type: string; + + @Column() + points: number; + + @Column() + stock: number; + + @Column() + status: string; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + UpdatedAt: Date; +} diff --git a/src/reward/reward.module.ts b/src/reward/reward.module.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/reward/reward.service.ts b/src/reward/reward.service.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/userReward/userReward.controllers.ts b/src/userReward/userReward.controllers.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/userReward/userReward.module.ts b/src/userReward/userReward.module.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/userReward/userReward.service.ts b/src/userReward/userReward.service.ts new file mode 100644 index 0000000..e69de29 From 72ad1ffcfe875d1e3cf2019f8a91164fd1b82dd1 Mon Sep 17 00:00:00 2001 From: Potsawee Date: Tue, 12 Nov 2024 13:51:19 +0700 Subject: [PATCH 021/155] change slug enum to category directory --- src/category/category.entity.ts | 2 +- src/category/dtos/category-response.dto.ts | 2 +- src/category/dtos/create-cateory.dto.ts | 2 +- src/category/dtos/update-category.dto.ts | 2 +- src/{shared => category}/enums/slug.enum.ts | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename src/{shared => category}/enums/slug.enum.ts (100%) diff --git a/src/category/category.entity.ts b/src/category/category.entity.ts index f2521b5..fd98cd0 100644 --- a/src/category/category.entity.ts +++ b/src/category/category.entity.ts @@ -1,4 +1,4 @@ -import { Slug } from 'src/shared/enums/slug.enum'; +import { Slug } from 'src/category/enums/slug.enum'; import { Entity } from 'typeorm'; import { Column, diff --git a/src/category/dtos/category-response.dto.ts b/src/category/dtos/category-response.dto.ts index cf36ef3..cb4d5ef 100644 --- a/src/category/dtos/category-response.dto.ts +++ b/src/category/dtos/category-response.dto.ts @@ -1,4 +1,4 @@ -import { Slug } from 'src/shared/enums/slug.enum'; +import { Slug } from 'src/category/enums/slug.enum'; import { Category } from '../category.entity'; export class categoryResponseDto { diff --git a/src/category/dtos/create-cateory.dto.ts b/src/category/dtos/create-cateory.dto.ts index 928910e..05a6e6a 100644 --- a/src/category/dtos/create-cateory.dto.ts +++ b/src/category/dtos/create-cateory.dto.ts @@ -5,7 +5,7 @@ import { IsString, } from '@nestjs/class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Slug } from 'src/shared/enums/slug.enum'; +import { Slug } from 'src/category/enums/slug.enum'; export class CreateCategoryDto { @IsNotEmpty() diff --git a/src/category/dtos/update-category.dto.ts b/src/category/dtos/update-category.dto.ts index b2494b6..5156292 100644 --- a/src/category/dtos/update-category.dto.ts +++ b/src/category/dtos/update-category.dto.ts @@ -1,6 +1,6 @@ import { IsOptional, IsString, IsEnum } from '@nestjs/class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Slug } from 'src/shared/enums/slug.enum'; +import { Slug } from 'src/category/enums/slug.enum'; export class updateCategoryDto { @IsOptional() diff --git a/src/shared/enums/slug.enum.ts b/src/category/enums/slug.enum.ts similarity index 100% rename from src/shared/enums/slug.enum.ts rename to src/category/enums/slug.enum.ts From 615469fdfea38ca4d16daf2747d085f93d75a815 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Tue, 12 Nov 2024 14:59:25 +0700 Subject: [PATCH 022/155] chore: Refactor user retrieval in AuthService to use findByEmail method --- src/auth/auth.service.ts | 2 +- src/user/user.service.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index ddec553..f24cb89 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -43,7 +43,7 @@ export class AuthService { } async register(registerDto: RegisterDto): Promise { - const user = await this.userService.findOne({ where: { email: registerDto.email } }); + const user = await this.userService.findByEmail(registerDto.email); if (user) throw new BadRequestException("User already exists"); const hashedPassword = await hash(registerDto.password); diff --git a/src/user/user.service.ts b/src/user/user.service.ts index e720f39..42fbfcb 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -24,6 +24,10 @@ export class UserService { return user; } + async findByEmail(email: string): Promise { + return await this.userRepository.findOne({ where: { email } }); + } + async create(createUserDto: CreateUserDto): Promise { try { return this.userRepository.save(createUserDto); From 7470b2f8a9761a72c33bed727b6d35f3fbc6cdc3 Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Wed, 13 Nov 2024 10:01:32 +0700 Subject: [PATCH 023/155] feat: Add Course module with DTOs, entity, service, and controller --- package-lock.json | 10196 +++++++++++++++++++++++ src/app.module.ts | 2 + src/course/course.controller.ts | 108 + src/course/course.entity.ts | 75 + src/course/course.module.ts | 18 + src/course/course.provider.ts | 9 + src/course/course.service.ts | 62 + src/course/dtos/course-response.dto.ts | 118 + src/course/dtos/create-course.dto.ts | 73 + src/course/dtos/index.ts | 3 + src/course/dtos/update-course.dto.ts | 5 + src/shared/enums/course-level.enum.ts | 5 + src/shared/enums/course-status.enum.ts | 5 + src/shared/enums/index.ts | 4 + src/user/user.controller.ts | 2 +- src/user/user.entity.ts | 6 +- 16 files changed, 10689 insertions(+), 2 deletions(-) create mode 100644 package-lock.json create mode 100644 src/course/course.controller.ts create mode 100644 src/course/course.entity.ts create mode 100644 src/course/course.module.ts create mode 100644 src/course/course.provider.ts create mode 100644 src/course/course.service.ts create mode 100644 src/course/dtos/course-response.dto.ts create mode 100644 src/course/dtos/create-course.dto.ts create mode 100644 src/course/dtos/index.ts create mode 100644 src/course/dtos/update-course.dto.ts create mode 100644 src/shared/enums/course-level.enum.ts create mode 100644 src/shared/enums/course-status.enum.ts create mode 100644 src/shared/enums/index.ts diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..3e14cc5 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,10196 @@ +{ + "name": "edusaig-api", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "edusaig-api", + "version": "0.0.1", + "license": "UNLICENSED", + "dependencies": { + "@nestjs/class-validator": "0.13.1", + "@nestjs/common": "^10.0.0", + "@nestjs/config": "^3.3.0", + "@nestjs/core": "^10.0.0", + "@nestjs/jwt": "^10.2.0", + "@nestjs/mapped-types": "^2.0.6", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/swagger": "^8.0.5", + "@nestjs/typeorm": "^10.0.2", + "argon2": "^0.41.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", + "dotenv": "^16.4.5", + "joi": "^17.13.3", + "mysql2": "^3.11.4", + "pg": "^8.13.1", + "reflect-metadata": "^0.2.0", + "rxjs": "^7.8.1", + "typeorm": "^0.3.20", + "typeorm-extension": "^3.6.3" + }, + "devDependencies": { + "@nestjs/cli": "^10.0.0", + "@nestjs/schematics": "^10.0.0", + "@nestjs/testing": "^10.0.0", + "@types/express": "^5.0.0", + "@types/jest": "^29.5.2", + "@types/node": "^20.3.1", + "@types/supertest": "^6.0.0", + "@typescript-eslint/eslint-plugin": "^8.0.0", + "@typescript-eslint/parser": "^8.0.0", + "eslint": "^8.0.0", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-prettier": "^5.0.0", + "jest": "^29.5.0", + "prettier": "^3.0.0", + "source-map-support": "^0.5.21", + "supertest": "^7.0.0", + "ts-jest": "^29.1.0", + "ts-loader": "^9.4.3", + "ts-node": "^10.9.1", + "tsconfig-paths": "^4.2.0", + "typescript": "^5.1.3" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular-devkit/core": { + "version": "17.3.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.11.tgz", + "integrity": "sha512-vTNDYNsLIWpYk2I969LMQFH29GTsLzxNk/0cLw5q56ARF0v5sIWfHYwGTS88jdDqIpuuettcSczbxeA7EuAmqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.1", + "picomatch": "4.0.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "17.3.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.11.tgz", + "integrity": "sha512-I5wviiIqiFwar9Pdk30Lujk8FczEEc18i22A5c6Z9lbmhPQdTroDnEQdsfXjy404wPe8H62s0I15o4pmMGfTYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "17.3.11", + "jsonc-parser": "3.2.1", + "magic-string": "0.30.8", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/schematics-cli": { + "version": "17.3.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-17.3.11.tgz", + "integrity": "sha512-kcOMqp+PHAKkqRad7Zd7PbpqJ0LqLaNZdY1+k66lLWmkEBozgq8v4ASn/puPWf9Bo0HpCiK+EzLf0VHE8Z/y6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "17.3.11", + "@angular-devkit/schematics": "17.3.11", + "ansi-colors": "4.1.3", + "inquirer": "9.2.15", + "symbol-observable": "4.0.0", + "yargs-parser": "21.1.1" + }, + "bin": { + "schematics": "bin/schematics.js" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/inquirer": { + "version": "9.2.15", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.15.tgz", + "integrity": "sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ljharb/through": "^2.3.12", + "ansi-escapes": "^4.3.2", + "chalk": "^5.3.0", + "cli-cursor": "^3.1.0", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "figures": "^3.2.0", + "lodash": "^4.17.21", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@angular-devkit/schematics-cli/node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", + "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", + "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.26.2", + "@babel/types": "^7.26.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", + "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", + "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", + "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/generator": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/template": "^7.25.9", + "@babel/types": "^7.25.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", + "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@faker-js/faker": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz", + "integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "license": "MIT", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0", + "npm": ">=6.14.13" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@ljharb/through": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", + "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@microsoft/tsdoc": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.0.tgz", + "integrity": "sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==", + "license": "MIT" + }, + "node_modules/@nestjs/class-validator": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/@nestjs/class-validator/-/class-validator-0.13.1.tgz", + "integrity": "sha512-Wwpg9KuGnkWOFleBPEFdHQKwqZsF/PFWQaeSaXwatkofgqD7N6AV5J30xIVgScDP+IcE+MdPGZJ38vHQ24KxPQ==", + "license": "MIT", + "dependencies": { + "@types/validator": "^13.1.3", + "libphonenumber-js": "^1.9.7", + "validator": "^13.5.2" + } + }, + "node_modules/@nestjs/cli": { + "version": "10.4.7", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.7.tgz", + "integrity": "sha512-4wJTtBJsbvjLIzXl+Qj6DYHv4J7abotuXyk7bes5erL79y+KBT61LulL56SqilzmNnHOAVbXcSXOn9S2aWUn6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "17.3.11", + "@angular-devkit/schematics": "17.3.11", + "@angular-devkit/schematics-cli": "17.3.11", + "@nestjs/schematics": "^10.0.1", + "chalk": "4.1.2", + "chokidar": "3.6.0", + "cli-table3": "0.6.5", + "commander": "4.1.1", + "fork-ts-checker-webpack-plugin": "9.0.2", + "glob": "10.4.2", + "inquirer": "8.2.6", + "node-emoji": "1.11.0", + "ora": "5.4.1", + "tree-kill": "1.2.2", + "tsconfig-paths": "4.2.0", + "tsconfig-paths-webpack-plugin": "4.1.0", + "typescript": "5.6.3", + "webpack": "5.96.1", + "webpack-node-externals": "3.0.0" + }, + "bin": { + "nest": "bin/nest.js" + }, + "engines": { + "node": ">= 16.14" + }, + "peerDependencies": { + "@swc/cli": "^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0", + "@swc/core": "^1.3.62" + }, + "peerDependenciesMeta": { + "@swc/cli": { + "optional": true + }, + "@swc/core": { + "optional": true + } + } + }, + "node_modules/@nestjs/common": { + "version": "10.4.7", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.7.tgz", + "integrity": "sha512-gIOpjD3Mx8gfYGxYm/RHPcJzqdknNNFCyY+AxzBT3gc5Xvvik1Dn5OxaMGw5EbVfhZgJKVP0n83giUOAlZQe7w==", + "license": "MIT", + "dependencies": { + "iterare": "1.2.1", + "tslib": "2.7.0", + "uid": "2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "class-transformer": "*", + "class-validator": "*", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/config": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.3.0.tgz", + "integrity": "sha512-pdGTp8m9d0ZCrjTpjkUbZx6gyf2IKf+7zlkrPNMsJzYZ4bFRRTpXrnj+556/5uiI6AfL5mMrJc2u7dB6bvM+VA==", + "license": "MIT", + "dependencies": { + "dotenv": "16.4.5", + "dotenv-expand": "10.0.0", + "lodash": "4.17.21" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "rxjs": "^7.1.0" + } + }, + "node_modules/@nestjs/core": { + "version": "10.4.7", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.7.tgz", + "integrity": "sha512-AIpQzW/vGGqSLkKvll1R7uaSNv99AxZI2EFyVJPNGDgFsfXaohfV1Ukl6f+s75Km+6Fj/7aNl80EqzNWQCS8Ig==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@nuxtjs/opencollective": "0.3.2", + "fast-safe-stringify": "2.1.1", + "iterare": "1.2.1", + "path-to-regexp": "3.3.0", + "tslib": "2.7.0", + "uid": "2.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/microservices": "^10.0.0", + "@nestjs/platform-express": "^10.0.0", + "@nestjs/websockets": "^10.0.0", + "reflect-metadata": "^0.1.12 || ^0.2.0", + "rxjs": "^7.1.0" + }, + "peerDependenciesMeta": { + "@nestjs/microservices": { + "optional": true + }, + "@nestjs/platform-express": { + "optional": true + }, + "@nestjs/websockets": { + "optional": true + } + } + }, + "node_modules/@nestjs/jwt": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-10.2.0.tgz", + "integrity": "sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==", + "license": "MIT", + "dependencies": { + "@types/jsonwebtoken": "9.0.5", + "jsonwebtoken": "9.0.2" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/@nestjs/mapped-types": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.6.tgz", + "integrity": "sha512-84ze+CPfp1OWdpRi1/lOu59hOhTz38eVzJvRKrg9ykRFwDz+XleKfMsG0gUqNZYFa6v53XYzeD+xItt8uDW7NQ==", + "license": "MIT", + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "class-transformer": "^0.4.0 || ^0.5.0", + "class-validator": "^0.13.0 || ^0.14.0", + "reflect-metadata": "^0.1.12 || ^0.2.0" + }, + "peerDependenciesMeta": { + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/platform-express": { + "version": "10.4.7", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.7.tgz", + "integrity": "sha512-q6XDOxZPTZ9cxALcVuKUlRBk+cVEv6dW2S8p2yVre22kpEQxq53/OI8EseDvzObGb6hepZ8+yBY04qoYqSlXNQ==", + "license": "MIT", + "dependencies": { + "body-parser": "1.20.3", + "cors": "2.8.5", + "express": "4.21.1", + "multer": "1.4.4-lts.1", + "tslib": "2.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0" + } + }, + "node_modules/@nestjs/schematics": { + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.2.3.tgz", + "integrity": "sha512-4e8gxaCk7DhBxVUly2PjYL4xC2ifDFexCqq1/u4TtivLGXotVk0wHdYuPYe1tHTHuR1lsOkRbfOCpkdTnigLVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "17.3.11", + "@angular-devkit/schematics": "17.3.11", + "comment-json": "4.2.5", + "jsonc-parser": "3.3.1", + "pluralize": "8.0.0" + }, + "peerDependencies": { + "typescript": ">=4.8.2" + } + }, + "node_modules/@nestjs/schematics/node_modules/jsonc-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nestjs/swagger": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-8.0.5.tgz", + "integrity": "sha512-ZmBdsbQNs3wIN5kCuvAVbz3/ULh3gi814oHTP49uTqAGi1aT0YSatUyncwQOHBOlRT+rwF+TNjoAsZ+twIk/Jw==", + "license": "MIT", + "dependencies": { + "@microsoft/tsdoc": "^0.15.0", + "@nestjs/mapped-types": "2.0.6", + "js-yaml": "4.1.0", + "lodash": "4.17.21", + "path-to-regexp": "3.3.0", + "swagger-ui-dist": "5.18.2" + }, + "peerDependencies": { + "@fastify/static": "^6.0.0 || ^7.0.0", + "@nestjs/common": "^9.0.0 || ^10.0.0", + "@nestjs/core": "^9.0.0 || ^10.0.0", + "class-transformer": "*", + "class-validator": "*", + "reflect-metadata": "^0.1.12 || ^0.2.0" + }, + "peerDependenciesMeta": { + "@fastify/static": { + "optional": true + }, + "class-transformer": { + "optional": true + }, + "class-validator": { + "optional": true + } + } + }, + "node_modules/@nestjs/testing": { + "version": "10.4.7", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.7.tgz", + "integrity": "sha512-aS3sQ0v4g8cyHDzW3xJv1+8MiFAkxUNXmnau588IFFI/nBIo/kevLNHNPr85keYekkJ/lwNDW72h8UGg8BYd9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "2.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nest" + }, + "peerDependencies": { + "@nestjs/common": "^10.0.0", + "@nestjs/core": "^10.0.0", + "@nestjs/microservices": "^10.0.0", + "@nestjs/platform-express": "^10.0.0" + }, + "peerDependenciesMeta": { + "@nestjs/microservices": { + "optional": true + }, + "@nestjs/platform-express": { + "optional": true + } + } + }, + "node_modules/@nestjs/typeorm": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-10.0.2.tgz", + "integrity": "sha512-H738bJyydK4SQkRCTeh1aFBxoO1E9xdL/HaLGThwrqN95os5mEyAtK7BLADOS+vldP4jDZ2VQPLj4epWwRqCeQ==", + "license": "MIT", + "dependencies": { + "uuid": "9.0.1" + }, + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0", + "@nestjs/core": "^8.0.0 || ^9.0.0 || ^10.0.0", + "reflect-metadata": "^0.1.13 || ^0.2.0", + "rxjs": "^7.2.0", + "typeorm": "^0.3.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nuxtjs/opencollective": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz", + "integrity": "sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "consola": "^2.15.0", + "node-fetch": "^2.6.1" + }, + "bin": { + "opencollective": "bin/opencollective.js" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/@phc/format": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@phc/format/-/format-1.0.0.tgz", + "integrity": "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sqltools/formatter": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", + "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==", + "license": "MIT" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.1.tgz", + "integrity": "sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz", + "integrity": "sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.17.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.6.tgz", + "integrity": "sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/qs": { + "version": "6.9.17", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", + "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/superagent": { + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/supertest": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.2.tgz", + "integrity": "sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" + } + }, + "node_modules/@types/validator": { + "version": "13.12.2", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz", + "integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==", + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.13.0.tgz", + "integrity": "sha512-nQtBLiZYMUPkclSeC3id+x4uVd1SGtHuElTxL++SfP47jR0zfkZBJHc+gL4qPsgTuypz0k8Y2GheaDYn6Gy3rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.13.0", + "@typescript-eslint/type-utils": "8.13.0", + "@typescript-eslint/utils": "8.13.0", + "@typescript-eslint/visitor-keys": "8.13.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.13.0.tgz", + "integrity": "sha512-w0xp+xGg8u/nONcGw1UXAr6cjCPU1w0XVyBs6Zqaj5eLmxkKQAByTdV/uGgNN5tVvN/kKpoQlP2cL7R+ajZZIQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "8.13.0", + "@typescript-eslint/types": "8.13.0", + "@typescript-eslint/typescript-estree": "8.13.0", + "@typescript-eslint/visitor-keys": "8.13.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.13.0.tgz", + "integrity": "sha512-XsGWww0odcUT0gJoBZ1DeulY1+jkaHUciUq4jKNv4cpInbvvrtDoyBH9rE/n2V29wQJPk8iCH1wipra9BhmiMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.13.0", + "@typescript-eslint/visitor-keys": "8.13.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.13.0.tgz", + "integrity": "sha512-Rqnn6xXTR316fP4D2pohZenJnp+NwQ1mo7/JM+J1LWZENSLkJI8ID8QNtlvFeb0HnFSK94D6q0cnMX6SbE5/vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.13.0", + "@typescript-eslint/utils": "8.13.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.13.0.tgz", + "integrity": "sha512-4cyFErJetFLckcThRUFdReWJjVsPCqyBlJTi6IDEpc1GWCIIZRFxVppjWLIMcQhNGhdWJJRYFHpHoDWvMlDzng==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.13.0.tgz", + "integrity": "sha512-v7SCIGmVsRK2Cy/LTLGN22uea6SaUIlpBcO/gnMGT/7zPtxp90bphcGf4fyrCQl3ZtiBKqVTG32hb668oIYy1g==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "8.13.0", + "@typescript-eslint/visitor-keys": "8.13.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.13.0.tgz", + "integrity": "sha512-A1EeYOND6Uv250nybnLZapeXpYMl8tkzYUxqmoKAWnI4sei3ihf2XdZVd+vVOmHGcp3t+P7yRrNsyyiXTvShFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.13.0", + "@typescript-eslint/types": "8.13.0", + "@typescript-eslint/typescript-estree": "8.13.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.13.0.tgz", + "integrity": "sha512-7N/+lztJqH4Mrf0lb10R/CbI1EaAMMGyF5y0oJvFoAhafwgiRA7TXyd8TFn8FC8k5y2dTsYogg238qavRGNnlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.13.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "devOptional": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/app-root-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", + "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/argon2": { + "version": "0.41.1", + "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.41.1.tgz", + "integrity": "sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@phc/format": "^1.0.0", + "node-addon-api": "^8.1.0", + "node-gyp-build": "^4.8.1" + }, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001679", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001679.tgz", + "integrity": "sha512-j2YqID/YwpLnKzCmBOS4tlZdWprXm3ZmQLBH9ZBXFOhoxLA46fwyBvx6toCBWBmnuwUY/qB3kEU6gFx8qgCroA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true, + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", + "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "license": "MIT" + }, + "node_modules/class-validator": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", + "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", + "license": "MIT", + "dependencies": { + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.10.53", + "validator": "^13.9.0" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "license": "ISC", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cli-highlight/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/comment-json": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.5.tgz", + "integrity": "sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/consola": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", + "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destr": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz", + "integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==", + "license": "MIT" + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ebec": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/ebec/-/ebec-2.3.0.tgz", + "integrity": "sha512-bt+0tSL7223VU3PSVi0vtNLZ8pO1AfWolcPPMk2a/a5H+o/ZU9ky0n3A0zhrR4qzJTN61uPsGIO4ShhOukdzxA==", + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.55", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.55.tgz", + "integrity": "sha512-6maZ2ASDOTBtjt9FhqYPRnbvKU5tjG0IN9SztUOWYw2AzNDNpKJYLJmlK0/En4Hs/aiWnB+JZ+gW19PIGszgKg==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envix": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/envix/-/envix-1.5.0.tgz", + "integrity": "sha512-IOxTKT+tffjxgvX2O5nq6enbkv6kBQ/QdMy18bZWo0P0rKPvsRp2/EypIPwTvJfnmk3VdOlq/KcRSZCswefM/w==", + "license": "MIT", + "dependencies": { + "std-env": "^3.7.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.10", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "license": "MIT" + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "license": "ISC" + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fork-ts-checker-webpack-plugin": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.0.2.tgz", + "integrity": "sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.16.7", + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "cosmiconfig": "^8.2.0", + "deepmerge": "^4.2.2", + "fs-extra": "^10.0.0", + "memfs": "^3.4.1", + "minimatch": "^3.0.4", + "node-abort-controller": "^3.0.1", + "schema-utils": "^3.1.1", + "semver": "^7.3.5", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">=12.13.0", + "yarn": ">=1.0.0" + }, + "peerDependencies": { + "typescript": ">3.6.0", + "webpack": "^5.11.0" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.2.tgz", + "integrity": "sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dezalgo": "^1.0.4", + "hexoid": "^2.0.0", + "once": "^1.4.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", + "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==", + "dev": true, + "license": "Unlicense" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz", + "integrity": "sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-own-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", + "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hexoid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-2.0.0.tgz", + "integrity": "sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/inquirer": { + "version": "8.2.6", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", + "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^6.0.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterare": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", + "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", + "license": "ISC", + "engines": { + "node": ">=6" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jake/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-config/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-runtime/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.0.tgz", + "integrity": "sha512-H5UpaUI+aHOqZXlYOaFP/8AzKsg+guWu+Pr3Y8i7+Y3zr1aXAvCvTAQ1RxSc6oVD8R8c7brgNtTVP91E7upH/g==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/libphonenumber-js": { + "version": "1.11.14", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.14.tgz", + "integrity": "sha512-sexvAfwcW1Lqws4zFp8heAtAEXbEDnvkYCEGzvOoMgZR7JhXo/IkE9MkkGACgBed5fWqh3ShBGnJBdDnU9N8EQ==", + "license": "MIT" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/locter": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/locter/-/locter-2.1.5.tgz", + "integrity": "sha512-eI57PuVxigQ0GBscGIIFGPB467E5zKODHD3XGuknzLvf7HdnvRw3GdZVGj1J8XKsKOYovZQesX/oOdTwbdjwuQ==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.3", + "ebec": "^2.3.0", + "fast-glob": "^3.3.2", + "flat": "^5.0.2", + "jiti": "^2.4.0", + "yaml": "^2.6.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "license": "Apache-2.0" + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lru.min": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.1.tgz", + "integrity": "sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, + "node_modules/magic-string": { + "version": "0.30.8", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", + "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "license": "Unlicense", + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multer": { + "version": "1.4.4-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4-lts.1.tgz", + "integrity": "sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true, + "license": "ISC" + }, + "node_modules/mysql2": { + "version": "3.11.4", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.11.4.tgz", + "integrity": "sha512-Z2o3tY4Z8EvSRDwknaC40MdZ3+m0sKbpnXrShQLdxPrAvcNli7jLrD2Zd2IzsRMw4eK9Yle500FDmlkIqp+krg==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "license": "MIT", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/named-placeholders/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-addon-api": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.2.2.tgz", + "integrity": "sha512-9emqXAKhVoNrQ792nLI/wpzPpJ/bj/YXxW0CvAau1+RdGBcCRF1Dmz7719zgVsQNrzHl9Tzn3ImZ4qWFarWL0A==", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/node-emoji": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", + "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.2.tgz", + "integrity": "sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "license": "MIT" + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "license": "MIT", + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "license": "MIT" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pg": { + "version": "8.13.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.1.tgz", + "integrity": "sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.7.0", + "pg-pool": "^3.7.0", + "pg-protocol": "^1.7.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.1.1" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz", + "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.0.tgz", + "integrity": "sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.0.tgz", + "integrity": "sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", + "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/rapiq": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/rapiq/-/rapiq-0.9.0.tgz", + "integrity": "sha512-k4oT4RarFBrlLMJ49xUTeQpa/us0uU4I70D/UEnK3FWQ4GENzei01rEQAmvPKAIzACo4NMW+YcYJ7EVfSa7EFg==", + "license": "MIT", + "dependencies": { + "ebec": "^1.1.0", + "smob": "^1.4.0" + } + }, + "node_modules/rapiq/node_modules/ebec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ebec/-/ebec-1.1.1.tgz", + "integrity": "sha512-JZ1vcvPQtR+8LGbZmbjG21IxLQq/v47iheJqn2F6yB2CgnGfn8ZVg3myHrf3buIZS8UCwQK0jOSIb3oHX7aH8g==", + "license": "MIT", + "dependencies": { + "smob": "^1.4.0" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "license": "(MIT AND BSD-3-Clause)", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/smob": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", + "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/std-env": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.8.0.tgz", + "integrity": "sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==", + "license": "MIT" + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/superagent": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", + "integrity": "sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^3.5.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/supertest": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.0.0.tgz", + "integrity": "sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "methods": "^1.1.2", + "superagent": "^9.0.1" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.18.2.tgz", + "integrity": "sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/synckit": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "5.36.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", + "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.0.tgz", + "integrity": "sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-jest": { + "version": "29.2.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", + "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.6.3", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-loader": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.1.tgz", + "integrity": "sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tsconfig-paths-webpack-plugin": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz", + "integrity": "sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tsconfig-paths": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/typeorm": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.20.tgz", + "integrity": "sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q==", + "license": "MIT", + "dependencies": { + "@sqltools/formatter": "^1.2.5", + "app-root-path": "^3.1.0", + "buffer": "^6.0.3", + "chalk": "^4.1.2", + "cli-highlight": "^2.1.11", + "dayjs": "^1.11.9", + "debug": "^4.3.4", + "dotenv": "^16.0.3", + "glob": "^10.3.10", + "mkdirp": "^2.1.3", + "reflect-metadata": "^0.2.1", + "sha.js": "^2.4.11", + "tslib": "^2.5.0", + "uuid": "^9.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "typeorm": "cli.js", + "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js", + "typeorm-ts-node-esm": "cli-ts-node-esm.js" + }, + "engines": { + "node": ">=16.13.0" + }, + "funding": { + "url": "https://opencollective.com/typeorm" + }, + "peerDependencies": { + "@google-cloud/spanner": "^5.18.0", + "@sap/hana-client": "^2.12.25", + "better-sqlite3": "^7.1.2 || ^8.0.0 || ^9.0.0", + "hdb-pool": "^0.1.6", + "ioredis": "^5.0.4", + "mongodb": "^5.8.0", + "mssql": "^9.1.1 || ^10.0.1", + "mysql2": "^2.2.5 || ^3.0.1", + "oracledb": "^6.3.0", + "pg": "^8.5.1", + "pg-native": "^3.0.0", + "pg-query-stream": "^4.0.0", + "redis": "^3.1.1 || ^4.0.0", + "sql.js": "^1.4.0", + "sqlite3": "^5.0.3", + "ts-node": "^10.7.0", + "typeorm-aurora-data-api-driver": "^2.0.0" + }, + "peerDependenciesMeta": { + "@google-cloud/spanner": { + "optional": true + }, + "@sap/hana-client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "hdb-pool": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mssql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "pg-query-stream": { + "optional": true + }, + "redis": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "ts-node": { + "optional": true + }, + "typeorm-aurora-data-api-driver": { + "optional": true + } + } + }, + "node_modules/typeorm-extension": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/typeorm-extension/-/typeorm-extension-3.6.3.tgz", + "integrity": "sha512-AE+8KqBphlBdVz5JS77o6LZzzi+b+YFFt8So4Qu/KRo/iynAwekrx98Oxuu3FAYNm6DUKDcubOBMZsJeiRvHkA==", + "license": "MIT", + "dependencies": { + "@faker-js/faker": "^8.4.1", + "consola": "^3.2.3", + "envix": "^1.5.0", + "locter": "^2.1.5", + "pascal-case": "^3.1.2", + "rapiq": "^0.9.0", + "reflect-metadata": "^0.2.2", + "smob": "^1.5.0", + "yargs": "^17.7.2" + }, + "bin": { + "typeorm-extension": "bin/cli.cjs", + "typeorm-extension-esm": "bin/cli.mjs" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "typeorm": "~0.3.0" + } + }, + "node_modules/typeorm-extension/node_modules/consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/typeorm/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/typeorm/node_modules/mkdirp": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", + "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typescript": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uid": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", + "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "license": "MIT", + "dependencies": { + "@lukeed/csprng": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/watchpack": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", + "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/webpack": { + "version": "5.96.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz", + "integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.6", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.14.0", + "browserslist": "^4.24.0", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.1", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-node-externals": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", + "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz", + "integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/src/app.module.ts b/src/app.module.ts index 0507d60..c1f365a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -17,6 +17,7 @@ import { AuthGuard } from './auth/auth.guard'; import { UserStreak } from './user-streak/user-streak.entity'; import { UserStreakModule } from './user-streak/user-streak.module'; import { RolesGuard } from './shared/guards/role.guard'; +import { CourseModule } from './course/course.module'; const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); @@ -44,6 +45,7 @@ const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); DatabaseModule, UserModule, UserStreakModule, + CourseModule, ], controllers: [AppController], providers: [ diff --git a/src/course/course.controller.ts b/src/course/course.controller.ts new file mode 100644 index 0000000..0e215c0 --- /dev/null +++ b/src/course/course.controller.ts @@ -0,0 +1,108 @@ +import { Body, Controller, Delete, Get, HttpStatus, Injectable, Param, ParseUUIDPipe, Patch, Post, Req } from "@nestjs/common"; +import { ApiBearerAuth, ApiParam, ApiResponse, ApiTags } from "@nestjs/swagger"; +import { CourseService } from "./course.service"; +import { CourseResponseDto, UpdateCourseDto } from "./dtos/index"; +import { AuthenticatedRequest } from "src/auth/interfaces/authenticated-request.interface"; +import { Roles } from "src/shared/decorators/role.decorator"; +import { Role } from "src/shared/enums"; +import { UserResponseDto } from "src/user/dtos/user-response.dto"; + +@Controller("course") +@ApiTags("Course") +@ApiBearerAuth() +@Injectable() +export class CourseController { + constructor(private readonly courseService : CourseService) { } + + @Get() + @ApiResponse({ + status: HttpStatus.OK, + type: CourseResponseDto, + description: 'Get all course', + isArray: true, + }) + async findAll(): Promise { + const courses = await this.courseService.findAll(); + return courses.map((course) => new CourseResponseDto(course)); + } + + @Get(':id') + @ApiParam({ + name: 'id', + type: String, + description: 'Course id', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: CourseResponseDto, + description: 'Get course by id', + }) + async findOne( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const course = await this.courseService.findOne({ where: { id } }); + return new CourseResponseDto(course); + } + + @Patch(':id') + @Roles(Role.TEACHER) + @ApiParam({ + name: 'id', + type: String, + description: 'Course id', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: UserResponseDto, + description: 'Update course by id', + }) + async update( + @Req() request: AuthenticatedRequest, + @Body() updateCourseDto:UpdateCourseDto, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + + ): Promise { + const course = await this.courseService.update(id,request.user.id, updateCourseDto); + return new CourseResponseDto(course); + } + + + @Delete(':id') + @Roles(Role.TEACHER) + @ApiParam({ + name: 'id', + type: String, + description: 'Course id', + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Delete course by id', + }) + async delete( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + await this.courseService.delete(id,request.user.id); + } +} \ No newline at end of file diff --git a/src/course/course.entity.ts b/src/course/course.entity.ts new file mode 100644 index 0000000..676eebe --- /dev/null +++ b/src/course/course.entity.ts @@ -0,0 +1,75 @@ +import { Entity, JoinColumn, ManyToOne } from "typeorm"; +import { Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"; +import { User } from "src/user/user.entity"; +import { CourseLevel } from "src/shared/enums/course-level.enum"; +import { CourseStatus } from "src/shared/enums/course-status.enum"; + + +@Entity() +export class Course { + @PrimaryGeneratedColumn("uuid") + id: string; + @Column({ + type: String, + nullable: false, + }) + title: string; + @Column({ + type: String, + nullable: false, + }) + description: string; + + @Column({ name: 'teacher_id' }) + teacherId: string; + + @ManyToOne(() => User) + @JoinColumn({ name: 'teacher_id' }) + teacher: User; + + @Column({ + type: String, + nullable: false, + }) + thumbnail: string; + + @Column({ + type: Number, + nullable: false, + }) + duration: number; + + @Column({ + type: 'enum', + nullable: false, + enum: CourseLevel, + default: CourseLevel.BEGINNER, + }) + level: CourseLevel; + + @Column({ + type: Number, + nullable: false, + }) + price: number; + + @Column({ + type: 'enum', + nullable: false, + enum: CourseStatus, + default: CourseStatus.DRAFT, + }) + status: CourseStatus; + + @CreateDateColumn({ + type: "timestamp", + nullable: false, + }) + createdAt: Date; + + @UpdateDateColumn({ + type: "timestamp", + nullable: false, + }) + updatedAt: Date; +} \ No newline at end of file diff --git a/src/course/course.module.ts b/src/course/course.module.ts new file mode 100644 index 0000000..5d1efd1 --- /dev/null +++ b/src/course/course.module.ts @@ -0,0 +1,18 @@ +import { Module } from "@nestjs/common"; +import { CourseService } from "./course.service"; +import { CourseController } from "./course.controller"; +import { courseProviders } from "./course.provider"; +import { DatabaseModule } from "src/database/database.module"; + +@Module({ + imports: [ + DatabaseModule, + ], + controllers: [CourseController], + providers: [ + ...courseProviders, + CourseService + ], + exports: [CourseService], +}) +export class CourseModule { } \ No newline at end of file diff --git a/src/course/course.provider.ts b/src/course/course.provider.ts new file mode 100644 index 0000000..30a2cde --- /dev/null +++ b/src/course/course.provider.ts @@ -0,0 +1,9 @@ +import { DataSource } from "typeorm"; +import { Course } from "./course.entity"; + + +export const courseProviders = [{ + provide: 'CourseRepository', + useFactory: (dataSource: DataSource) => dataSource.getRepository(Course), + inject: ['DataSource'], +}]; \ No newline at end of file diff --git a/src/course/course.service.ts b/src/course/course.service.ts new file mode 100644 index 0000000..9a0cf28 --- /dev/null +++ b/src/course/course.service.ts @@ -0,0 +1,62 @@ +import { BadRequestException, Inject, Injectable, NotFoundException } from "@nestjs/common"; +import { FindOneOptions, Repository } from "typeorm"; +import { Course } from "./course.entity"; +import { UpdateCourseDto,CreateCourseDto } from "./dtos/index"; + + + +@Injectable() +export class CourseService { + constructor( + @Inject("CourseRepository") + private readonly courseRepository: Repository, + ) { } + + async findAll(): Promise { + return this.courseRepository.find(); + } + + async findOne(options: FindOneOptions): Promise { + const course = this.courseRepository.findOne(options); + if (!course) + throw new NotFoundException("Course not found"); + return course; + } + + async create(createCourseDto: CreateCourseDto): Promise { + try { + return this.courseRepository.save(createCourseDto); + } catch (error) { + if (error instanceof Error) + throw new BadRequestException(error.message); + } + } + + async update(id: string,userId: string, updateCourseDto: UpdateCourseDto): Promise { + try { + await this.checkOwnership(id, userId); + await this.courseRepository.update(id, updateCourseDto); + return await this.findOne({ where: { id } }); + } catch (error) { + if (error instanceof Error) + throw new NotFoundException("Course not found"); + } + } + + async delete(id: string, userId : string): Promise { + try { + await this.checkOwnership(id, userId); + await this.courseRepository.delete(id); + } catch (error) { + if (error instanceof Error) + throw new NotFoundException("Course not found"); + } + } + + private async checkOwnership(id: string, teacherId: string): Promise { + const course = await this.findOne({ where: { id } }); + if (course.teacher.id !== teacherId) + throw new BadRequestException("You don't own this course"); + } + +} \ No newline at end of file diff --git a/src/course/dtos/course-response.dto.ts b/src/course/dtos/course-response.dto.ts new file mode 100644 index 0000000..d0a1419 --- /dev/null +++ b/src/course/dtos/course-response.dto.ts @@ -0,0 +1,118 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { UserResponseDto } from "src/user/dtos/user-response.dto"; +import { Course } from "../course.entity"; +import { CourseLevel, CourseStatus } from "src/shared/enums"; + +export class CourseResponseDto { + @ApiProperty( + { + description: 'Course ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + id: string; + + @ApiProperty( + { + description: 'Course title', + type: String, + example: 'Introduction to Programming', + }) + title: string; + + @ApiProperty( + { + description: 'Course description', + type: String, + example: 'This course is an introduction to programming', + }) + description: string; + + @ApiProperty( + { + description: 'Teacher Data', + type: UserResponseDto, + example: { + id: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + email: 'johndoe@gmail.com', + role: 'teacher', + createdAt: new Date(), + updatedAt: new Date(), + fullname: 'John Doe', + } + }) + teacherData: UserResponseDto; + @ApiProperty( + { + description: 'Course thumbnail', + type: String, + example: 'https://www.example.com/thumbnail.jpg', + }) + thumbnail: string; + + @ApiProperty( + { + description: 'Course duration', + type: Number, + example: 60, + }) + duration: number; + + @ApiProperty( + { + description: 'Course level', + type: String, + example: CourseLevel.BEGINNER, + enum: CourseLevel, + }) + level: CourseLevel; + + @ApiProperty( + { + description: 'Course price', + type: Number, + example: 100, + }) + price: number; + + @ApiProperty( + { + description: 'Course status', + type: String, + example: CourseStatus.DRAFT, + enum: CourseStatus, + }) + status: CourseStatus; + + @ApiProperty( + { + description: 'Course created date', + type: Date, + example: new Date(), + }) + createdAt: Date; + + @ApiProperty( + { + description: 'Course updated date', + type: Date, + example: new Date(), + }) + updatedAt: Date; + + + constructor(course: Course) { + this.id = course.id; + this.title = course.title; + this.description = course.description; + this.teacherData = new UserResponseDto(course.teacher); + this.thumbnail = course.thumbnail; + this.duration = course.duration; + this.level = course.level; + this.price = course.price; + this.status = course.status; + this.createdAt = course.createdAt; + this.updatedAt = course.updatedAt; + } + +} \ No newline at end of file diff --git a/src/course/dtos/create-course.dto.ts b/src/course/dtos/create-course.dto.ts new file mode 100644 index 0000000..4bd6780 --- /dev/null +++ b/src/course/dtos/create-course.dto.ts @@ -0,0 +1,73 @@ + +import { ApiProperty } from "@nestjs/swagger"; +import { IsEnum, IsNotEmpty } from "class-validator"; +import { CourseLevel,CourseStatus } from "src/shared/enums/index"; +export class CreateCourseDto { + @IsNotEmpty() + @ApiProperty({ + description: 'Course title', + type: String, + example: 'Introduction to Programming', + }) + title: string; + + @IsNotEmpty() + @ApiProperty({ + description: 'Course description', + type: String, + example: 'This course is an introduction to programming', + }) + description: string; + @IsNotEmpty() + @ApiProperty({ + description: 'Teacher ID', + type: String, + example: '123456', + }) + teacherId: string; + + @IsNotEmpty() + @ApiProperty({ + description: 'Course thumbnail', + type: String, + example: 'https://www.example.com/thumbnail.jpg', + }) + thumbnail: string; + + @IsNotEmpty() + @ApiProperty({ + description: 'Course duration', + type: Number, + example: 60, + }) + duration: number; + + @IsNotEmpty() + @IsEnum(CourseLevel, {message: `Invalid level. Level should be either ${CourseLevel.BEGINNER}, ${CourseLevel.INTERMEDIATE}, or ${CourseLevel.ADVANCED}`}) + @ApiProperty({ + description: 'Course level', + type: String, + example: 'beginner', + enum: ['beginner', 'intermediate', 'advanced'], + }) + level: CourseLevel; + + @IsNotEmpty() + @ApiProperty({ + description: 'Course price', + type: Number, + example: 100, + }) + price: number; + + @IsNotEmpty() + @IsEnum(CourseStatus, {message: `Invalid status. Status should be either ${CourseStatus.DRAFT}, ${CourseStatus.PUBLISHED}, or ${CourseStatus.ARCHIVED}`}) + @IsNotEmpty() + @ApiProperty({ + description: 'Course status', + type: String, + example: 'draft', + enum: ['draft', 'published', 'archived'], + }) + status: CourseStatus; +} \ No newline at end of file diff --git a/src/course/dtos/index.ts b/src/course/dtos/index.ts new file mode 100644 index 0000000..bbbc275 --- /dev/null +++ b/src/course/dtos/index.ts @@ -0,0 +1,3 @@ +export * from './create-course.dto'; +export * from './update-course.dto'; +export * from './course-response.dto'; \ No newline at end of file diff --git a/src/course/dtos/update-course.dto.ts b/src/course/dtos/update-course.dto.ts new file mode 100644 index 0000000..eca89ee --- /dev/null +++ b/src/course/dtos/update-course.dto.ts @@ -0,0 +1,5 @@ +import { PartialType } from "@nestjs/swagger"; +import { CreateCourseDto } from "./index"; + +export class UpdateCourseDto extends PartialType(CreateCourseDto) {} + diff --git a/src/shared/enums/course-level.enum.ts b/src/shared/enums/course-level.enum.ts new file mode 100644 index 0000000..d56baa3 --- /dev/null +++ b/src/shared/enums/course-level.enum.ts @@ -0,0 +1,5 @@ +export enum CourseLevel { + BEGINNER = "beginner", + INTERMEDIATE = "intermediate", + ADVANCED = "advanced", +} diff --git a/src/shared/enums/course-status.enum.ts b/src/shared/enums/course-status.enum.ts new file mode 100644 index 0000000..291c29d --- /dev/null +++ b/src/shared/enums/course-status.enum.ts @@ -0,0 +1,5 @@ +export enum CourseStatus { + DRAFT = "draft", + PUBLISHED = "published", + ARCHIVED = "archived", +} diff --git a/src/shared/enums/index.ts b/src/shared/enums/index.ts new file mode 100644 index 0000000..23f3511 --- /dev/null +++ b/src/shared/enums/index.ts @@ -0,0 +1,4 @@ +export * from './course-level.enum'; +export * from './course-status.enum'; +export * from './roles.enum'; +export * from './environment.enum'; diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 21551d8..aff1115 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -33,7 +33,7 @@ export class UserController { type: UserResponseDto, description: 'Get user profile', }) - async getProfile( + async getProfile( @Req() request: AuthenticatedRequest, ): Promise { const user = await this.userService.findOne({ diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index c074118..713742d 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -1,6 +1,7 @@ -import { Entity } from "typeorm"; +import { Entity, OneToMany } from "typeorm"; import { Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"; import { Role } from "src/shared/enums/roles.enum"; +import { Course } from "src/course/course.entity"; @Entity() export class User { @@ -36,6 +37,9 @@ export class User { unique: true, }) email: string; + + @OneToMany(() => Course, (course) => course.teacher) + courses: Course[]; @CreateDateColumn({ type: "timestamp with time zone", From f29912e6d7ce8474f4a68fcf2827e7e2e50d9bba Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Wed, 13 Nov 2024 10:58:34 +0700 Subject: [PATCH 024/155] feat: Implement course creation endpoint and update database configuration --- src/course/course.controller.ts | 17 ++++++++++++++++- src/shared/configs/database.config.ts | 3 ++- src/user-streak/user-streak.controller.ts | 2 +- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/course/course.controller.ts b/src/course/course.controller.ts index 0e215c0..bf1943f 100644 --- a/src/course/course.controller.ts +++ b/src/course/course.controller.ts @@ -1,7 +1,7 @@ import { Body, Controller, Delete, Get, HttpStatus, Injectable, Param, ParseUUIDPipe, Patch, Post, Req } from "@nestjs/common"; import { ApiBearerAuth, ApiParam, ApiResponse, ApiTags } from "@nestjs/swagger"; import { CourseService } from "./course.service"; -import { CourseResponseDto, UpdateCourseDto } from "./dtos/index"; +import { CourseResponseDto, CreateCourseDto, UpdateCourseDto } from "./dtos/index"; import { AuthenticatedRequest } from "src/auth/interfaces/authenticated-request.interface"; import { Roles } from "src/shared/decorators/role.decorator"; import { Role } from "src/shared/enums"; @@ -51,6 +51,21 @@ export class CourseController { return new CourseResponseDto(course); } + + @Post() + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.CREATED, + type: CourseResponseDto, + description: 'Create course', + }) + async create( + @Body() createCourseDto: CreateCourseDto, + ): Promise { + const course = await this.courseService.create(createCourseDto); + return new CourseResponseDto(course); + } + @Patch(':id') @Roles(Role.TEACHER) @ApiParam({ diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index e9a41a0..d14e9bd 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -4,6 +4,7 @@ import { GLOBAL_CONFIG } from "../constants/global-config.constant"; import 'dotenv/config'; import { User } from "src/user/user.entity"; import { UserStreak } from "src/user-streak/user-streak.entity"; +import { Course } from "src/course/course.entity"; const configService = new ConfigService(); @@ -15,7 +16,7 @@ export const databaseConfig: DataSourceOptions = { password: configService.get(GLOBAL_CONFIG.DB_PASSWORD), database: configService.get(GLOBAL_CONFIG.DB_DATABASE), logging: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), - entities: [User, UserStreak], + entities: [User, UserStreak, Course], }; export default new DataSource(databaseConfig); \ No newline at end of file diff --git a/src/user-streak/user-streak.controller.ts b/src/user-streak/user-streak.controller.ts index 7bb6dd9..013fb3d 100644 --- a/src/user-streak/user-streak.controller.ts +++ b/src/user-streak/user-streak.controller.ts @@ -27,7 +27,7 @@ export class UserStreakController { @ApiResponse({ status: HttpStatus.OK, type: UserStreak, - description: 'Get all user streaks', + description: 'Get all user streaks', isArray: true, }) async findAll(): Promise { From 43bf59a4f3c74cb941b2ba72e6403daf44af5d2a Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Wed, 13 Nov 2024 11:09:33 +0700 Subject: [PATCH 025/155] chore: Remove role validation from CreateUserDto and set default role in User entity --- src/user/dtos/create-user.dto.ts | 11 ----------- src/user/user.entity.ts | 1 + 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/user/dtos/create-user.dto.ts b/src/user/dtos/create-user.dto.ts index 0590146..0fe5287 100644 --- a/src/user/dtos/create-user.dto.ts +++ b/src/user/dtos/create-user.dto.ts @@ -39,15 +39,4 @@ export class CreateUserDto { }) password: string; - @IsEnum(AvailableRoles, { - message: `Invalid role. Role should be either ${AvailableRoles.STUDENT} or ${AvailableRoles.TEACHER}`, - }) - @IsNotEmpty() - @ApiProperty({ - description: 'User role', - type: String, - example: AvailableRoles.STUDENT, - enum: AvailableRoles, - }) - role: Role.STUDENT | Role.TEACHER; } \ No newline at end of file diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index c074118..4824e08 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -22,6 +22,7 @@ export class User { type: "enum", enum: Role, nullable: false, + default: Role.STUDENT, }) role: Role; From 47e4b7ae0a3bbd6f0960100f8fcbe77aac2c6d97 Mon Sep 17 00:00:00 2001 From: khris-xp Date: Wed, 13 Nov 2024 16:17:44 +0700 Subject: [PATCH 026/155] feat: pagination query data function --- src/main.ts | 58 +++-- .../pagination/dtos/paginate-meta.dto.ts | 56 +++++ .../pagination/dtos/paginate-query.dto.ts | 39 ++++ .../pagination/dtos/paginate-response.dto.ts | 35 +++ src/shared/pagination/index.ts | 1 + src/shared/pagination/typeorm.ts | 61 +++++ src/user/dtos/create-user.dto.ts | 77 +++--- src/user/dtos/user-response.dto.ts | 125 +++++----- src/user/user.controller.ts | 221 ++++++++++-------- src/user/user.module.ts | 25 +- src/user/user.service.ts | 109 +++++---- 11 files changed, 537 insertions(+), 270 deletions(-) create mode 100644 src/shared/pagination/dtos/paginate-meta.dto.ts create mode 100644 src/shared/pagination/dtos/paginate-query.dto.ts create mode 100644 src/shared/pagination/dtos/paginate-response.dto.ts create mode 100644 src/shared/pagination/index.ts create mode 100644 src/shared/pagination/typeorm.ts diff --git a/src/main.ts b/src/main.ts index d2301a3..f3e748b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,39 +1,49 @@ -import { NestFactory } from '@nestjs/core'; -import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; +import { NestFactory } from '@nestjs/core'; +import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { createDatabase } from 'typeorm-extension'; +import { AppModule } from './app.module'; import { databaseConfig } from './shared/configs/database.config'; import { GLOBAL_CONFIG } from './shared/constants/global-config.constant'; -import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; async function bootstrap() { - const configService = new ConfigService(); + const configService = new ConfigService(); - await createDatabase({ - options: databaseConfig, - }); + await createDatabase({ + options: databaseConfig, + }); - const app = await NestFactory.create(AppModule); + const app = await NestFactory.create(AppModule); - app.useGlobalPipes(new ValidationPipe()); - app.enableCors({ - origin: configService.get(GLOBAL_CONFIG.CORS_ALLOW_ORIGIN), - methods: ['GET', 'POST', 'PATCH', 'DELETE'], - optionsSuccessStatus: 200, - exposedHeaders: 'Authorization', - }); + app.useGlobalPipes( + new ValidationPipe({ + transform: true, + transformOptions: { + enableImplicitConversion: true, + }, + whitelist: true, + forbidNonWhitelisted: true, + enableDebugMessages: true, + }), + ); + app.enableCors({ + origin: configService.get(GLOBAL_CONFIG.CORS_ALLOW_ORIGIN), + methods: ['GET', 'POST', 'PATCH', 'DELETE'], + optionsSuccessStatus: 200, + exposedHeaders: 'Authorization', + }); - const config = new DocumentBuilder() - .addBearerAuth() - .setTitle('Edusaig API') - .setDescription('This is the Edusaig API documentation') - .setVersion('1.0') - .build(); - const documentFactory = () => SwaggerModule.createDocument(app, config); - SwaggerModule.setup('docs', app, documentFactory); + const config = new DocumentBuilder() + .addBearerAuth() + .setTitle('Edusaig API') + .setDescription('This is the Edusaig API documentation') + .setVersion('1.0') + .build(); + const documentFactory = () => SwaggerModule.createDocument(app, config); + SwaggerModule.setup('docs', app, documentFactory); - await app.listen(configService.get(GLOBAL_CONFIG.PORT) ?? 3000); + await app.listen(configService.get(GLOBAL_CONFIG.PORT) ?? 3000); } bootstrap(); diff --git a/src/shared/pagination/dtos/paginate-meta.dto.ts b/src/shared/pagination/dtos/paginate-meta.dto.ts new file mode 100644 index 0000000..51a4908 --- /dev/null +++ b/src/shared/pagination/dtos/paginate-meta.dto.ts @@ -0,0 +1,56 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class PaginationMetaDto { + @ApiProperty({ + description: 'Total number of items', + type: Number, + example: 100, + }) + total: number; + + @ApiProperty({ + description: 'Number of items per page', + type: Number, + example: 10, + }) + pageSize: number; + + @ApiProperty({ + description: 'Current page number', + type: Number, + example: 1, + }) + currentPage: number; + + @ApiProperty({ + description: 'Next page number (null if no next page)', + type: Number, + nullable: true, + example: 2, + }) + nextPage: number | null; + + @ApiProperty({ + description: 'Previous page number (null if no previous page)', + type: Number, + nullable: true, + example: null, + }) + prevPage: number | null; + + @ApiProperty({ + description: 'Last page number', + type: Number, + example: 10, + }) + lastPage: number; + + constructor(total: number, pageSize: number, currentPage: number) { + this.total = total; + this.pageSize = pageSize; + this.currentPage = currentPage; + this.lastPage = Math.ceil(total / pageSize); + this.nextPage = currentPage < this.lastPage ? currentPage + 1 : null; + this.prevPage = currentPage > 1 ? currentPage - 1 : null; + } +} diff --git a/src/shared/pagination/dtos/paginate-query.dto.ts b/src/shared/pagination/dtos/paginate-query.dto.ts new file mode 100644 index 0000000..62ca12a --- /dev/null +++ b/src/shared/pagination/dtos/paginate-query.dto.ts @@ -0,0 +1,39 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { IsInt, IsOptional, IsString, Min } from 'class-validator'; + +export class PaginateQueryDto { + @ApiProperty({ + required: false, + default: 1, + description: 'Page number', + type: Number, + }) + @IsOptional() + @Transform(({ value }) => parseInt(value, 10)) + @IsInt() + @Min(1) + page: number = 1; + + @ApiProperty({ + required: false, + default: 10, + description: 'Items per page', + type: Number, + }) + @IsOptional() + @Transform(({ value }) => parseInt(value, 10)) + @IsInt() + @Min(1) + limit: number = 10; + + @ApiProperty({ + required: false, + default: '', + description: 'Search', + type: String, + }) + @IsOptional() + @IsString() + search: string = ''; +} diff --git a/src/shared/pagination/dtos/paginate-response.dto.ts b/src/shared/pagination/dtos/paginate-response.dto.ts new file mode 100644 index 0000000..208ae1d --- /dev/null +++ b/src/shared/pagination/dtos/paginate-response.dto.ts @@ -0,0 +1,35 @@ +import { Type } from '@nestjs/common'; +import { ApiProperty } from '@nestjs/swagger'; +import { PaginationMetaDto } from './paginate-meta.dto'; + +export interface IBaseEntity { + id: string; +} + +export function PaginatedResponse(ItemType: Type) { + class PaginatedResponseClass { + @ApiProperty({ + description: 'Array of items', + type: [ItemType], + }) + data: T[]; + + @ApiProperty({ + description: 'Pagination metadata', + type: PaginationMetaDto, + }) + meta: PaginationMetaDto; + + constructor( + items: T[], + total: number, + pageSize: number, + currentPage: number, + ) { + this.data = items; + this.meta = new PaginationMetaDto(total, pageSize, currentPage); + } + } + + return PaginatedResponseClass; +} diff --git a/src/shared/pagination/index.ts b/src/shared/pagination/index.ts new file mode 100644 index 0000000..aa5052b --- /dev/null +++ b/src/shared/pagination/index.ts @@ -0,0 +1 @@ +export * from './typeorm'; diff --git a/src/shared/pagination/typeorm.ts b/src/shared/pagination/typeorm.ts new file mode 100644 index 0000000..5276303 --- /dev/null +++ b/src/shared/pagination/typeorm.ts @@ -0,0 +1,61 @@ +import { BaseEntity, FindManyOptions, Repository } from 'typeorm'; + +class TypeORMPagination { + constructor( + private readonly repository: Repository, + private readonly queryOptions: FindManyOptions, + private readonly page: number, + private readonly limit: number, + ) {} + + protected format(data: [T[], number]) { + const [result, total] = data; + const lastPage = Math.ceil(total / this.limit); + const nextPage = this.page + 1 > lastPage ? null : this.page + 1; + const prevPage = this.page - 1 < 1 ? null : this.page - 1; + + const meta = { + total, + pageSize: Math.min(this.limit, total), + currentPage: this.page, + nextPage: nextPage, + prevPage: prevPage, + lastPage: lastPage, + }; + + return { + data: result, + meta, + }; + } + + async run() { + const data = await this.repository.findAndCount(this.queryOptions); + return this.format(data); + } +} +type Options = { + readonly page?: number; + readonly limit?: number; +}; +export async function createPagination( + repository: Repository, + { page = 1, limit = 20 }: Options, +) { + const _take = limit || 10; + const _page = page || 1; + const _skip = (_page - 1) * _take; + + const find = (options: FindManyOptions) => { + return new TypeORMPagination( + repository, + Object.assign(options, { + take: _take, + skip: _skip, + }), + _page, + _take, + ); + }; + return { find }; +} diff --git a/src/user/dtos/create-user.dto.ts b/src/user/dtos/create-user.dto.ts index 0fe5287..ee08eec 100644 --- a/src/user/dtos/create-user.dto.ts +++ b/src/user/dtos/create-user.dto.ts @@ -1,42 +1,45 @@ -import { IsString, IsEmail, IsStrongPassword, IsEnum, IsNotEmpty } from "class-validator"; -import { AvailableRoles, Role } from "src/shared/enums/roles.enum"; -import { ApiProperty } from "@nestjs/swagger"; +import { ApiProperty } from '@nestjs/swagger'; +import { + IsEmail, + IsNotEmpty, + IsString, + IsStrongPassword, +} from 'class-validator'; export class CreateUserDto { - @IsEmail() - @IsNotEmpty() - @ApiProperty({ - description: 'User email', - type: String, - example: 'johndoe@gmail.com', - }) - email: string; + @IsEmail() + @IsNotEmpty() + @ApiProperty({ + description: 'User email', + type: String, + example: 'johndoe@gmail.com', + }) + email: string; - @IsString() - @IsNotEmpty() - @ApiProperty({ - description: 'User username', - type: String, - example: 'johndoe', - }) - username: string; + @IsString() + @IsNotEmpty() + @ApiProperty({ + description: 'User username', + type: String, + example: 'johndoe', + }) + username: string; - @IsString() - @IsNotEmpty() - @ApiProperty({ - description: 'User fullname', - type: String, - example: 'John Doe', - }) - fullname: string; + @IsString() + @IsNotEmpty() + @ApiProperty({ + description: 'User fullname', + type: String, + example: 'John Doe', + }) + fullname: string; - @IsStrongPassword() - @IsNotEmpty() - @ApiProperty({ - description: 'User password', - type: String, - example: 'P@ssw0rd!', - }) - password: string; - -} \ No newline at end of file + @IsStrongPassword() + @IsNotEmpty() + @ApiProperty({ + description: 'User password', + type: String, + example: 'P@ssw0rd!', + }) + password: string; +} diff --git a/src/user/dtos/user-response.dto.ts b/src/user/dtos/user-response.dto.ts index 811ef38..fc0630c 100644 --- a/src/user/dtos/user-response.dto.ts +++ b/src/user/dtos/user-response.dto.ts @@ -1,57 +1,72 @@ -import { Role } from "src/shared/enums/roles.enum"; -import { User } from "../user.entity"; -import { ApiProperty } from "@nestjs/swagger"; +import { ApiProperty } from '@nestjs/swagger'; +import { Role } from 'src/shared/enums/roles.enum'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { User } from '../user.entity'; export class UserResponseDto { - @ApiProperty({ - description: 'User ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }) - id: string; - - @ApiProperty({ - description: 'User email', - type: String, - example: 'johndoe@gmail.com', - }) - email: string; - - @ApiProperty({ - description: 'User role', - type: String, - example: Role.STUDENT, - enum: [Role.STUDENT, Role.TEACHER], - }) - role: Role; - - @ApiProperty({ - description: 'User created date', - type: Date, - example: new Date(), - }) - createdAt: Date; - - @ApiProperty({ - description: 'User updated date', - type: Date, - example: new Date(), - }) - updatedAt: Date; - - @ApiProperty({ - description: 'User fullname', - type: String, - example: 'John Doe', - }) - fullname: string; - - constructor(user: User) { - this.id = user.id; - this.email = user.email; - this.role = user.role; - this.createdAt = user.createdAt; - this.updatedAt = user.updatedAt; - this.fullname = user.fullname; - } -} \ No newline at end of file + @ApiProperty({ + description: 'User ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + id: string; + + @ApiProperty({ + description: 'User email', + type: String, + example: 'johndoe@gmail.com', + }) + email: string; + + @ApiProperty({ + description: 'User role', + type: String, + example: Role.STUDENT, + enum: [Role.STUDENT, Role.TEACHER], + }) + role: Role; + + @ApiProperty({ + description: 'User created date', + type: Date, + example: new Date(), + }) + createdAt: Date; + + @ApiProperty({ + description: 'User updated date', + type: Date, + example: new Date(), + }) + updatedAt: Date; + + @ApiProperty({ + description: 'User fullname', + type: String, + example: 'John Doe', + }) + fullname: string; + + constructor(user: User) { + this.id = user.id; + this.email = user.email; + this.role = user.role; + this.createdAt = user.createdAt; + this.updatedAt = user.updatedAt; + this.fullname = user.fullname; + } +} + +export class PaginatedUserResponseDto extends PaginatedResponse( + UserResponseDto, +) { + constructor( + users: User[], + total: number, + pageSize: number, + currentPage: number, + ) { + const userDtos = users.map((user) => new UserResponseDto(user)); + super(userDtos, total, pageSize, currentPage); + } +} diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 21551d8..ccfe5cc 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -1,110 +1,143 @@ import { - Controller, - Injectable, - Get, - Req, - Param, - ParseUUIDPipe, - HttpStatus, - Patch, - Body, - Delete, - HttpCode, + Body, + Controller, + Delete, + Get, + HttpCode, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Query, + Req, } from '@nestjs/common'; +import { + ApiBearerAuth, + ApiParam, + ApiQuery, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; -import { UserService } from './user.service'; -import { UserResponseDto } from './dtos/user-response.dto'; -import { ApiTags, ApiResponse, ApiBearerAuth, ApiParam } from '@nestjs/swagger'; -import { UpdateUserDto } from './dtos/update-user.dto'; -import { Role } from 'src/shared/enums/roles.enum'; -import { Roles } from 'src/shared/decorators/role.decorator'; import { Public } from 'src/shared/decorators/public.decorator'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums/roles.enum'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { UpdateUserDto } from './dtos/update-user.dto'; +import { + PaginatedUserResponseDto, + UserResponseDto, +} from './dtos/user-response.dto'; +import { UserService } from './user.service'; @Controller('user') @ApiTags('User') @ApiBearerAuth() @Injectable() export class UserController { - constructor(private readonly userService: UserService) { } + constructor(private readonly userService: UserService) {} - @Get('profile') - @ApiResponse({ - status: HttpStatus.OK, - type: UserResponseDto, - description: 'Get user profile', - }) - async getProfile( - @Req() request: AuthenticatedRequest, - ): Promise { - const user = await this.userService.findOne({ - where: { id: request.user.id }, - }); - return new UserResponseDto(user); - } + @Get('profile') + @ApiResponse({ + status: HttpStatus.OK, + type: UserResponseDto, + description: 'Get user profile', + }) + async getProfile( + @Req() request: AuthenticatedRequest, + ): Promise { + const user = await this.userService.findOne({ + where: { id: request.user.id }, + }); + return new UserResponseDto(user); + } - @Get() - @Roles(Role.ADMIN) - @ApiResponse({ - status: HttpStatus.OK, - type: UserResponseDto, - description: 'Get all users', - isArray: true, - }) - async findAll(): Promise { - const users = await this.userService.findAll(); - return users.map((user) => new UserResponseDto(user)); - } + @Get() + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + type: PaginatedUserResponseDto, + description: 'Get all users', + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by email', + }) + async findAll( + @Query() query: PaginateQueryDto, + ): Promise { + return this.userService.findAll({ + page: query.page, + limit: query.limit, + search: query.search, + }); + } - @Get(':id') - @ApiParam({ - name: 'id', - type: String, - description: 'User id', - }) - @ApiResponse({ - status: HttpStatus.OK, - type: UserResponseDto, - description: 'Get user by id', - }) - @Public() - async findOne( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - ): Promise { - const user = await this.userService.findOne({ where: { id } }); - return new UserResponseDto(user); - } + @Get(':id') + @ApiParam({ + name: 'id', + type: String, + description: 'User id', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: UserResponseDto, + description: 'Get user by id', + }) + @Public() + async findOne( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const user = await this.userService.findOne({ where: { id } }); + return new UserResponseDto(user); + } - @Patch() - @ApiResponse({ - status: HttpStatus.OK, - type: UserResponseDto, - description: 'Update user', - }) - async update( - @Req() request: AuthenticatedRequest, - @Body() updateUserDto: UpdateUserDto, - ): Promise { - const user = await this.userService.update(request.user.id, updateUserDto); - return new UserResponseDto(user); - } + @Patch() + @ApiResponse({ + status: HttpStatus.OK, + type: UserResponseDto, + description: 'Update user', + }) + async update( + @Req() request: AuthenticatedRequest, + @Body() updateUserDto: UpdateUserDto, + ): Promise { + const user = await this.userService.update(request.user.id, updateUserDto); + return new UserResponseDto(user); + } - @Delete() - @ApiResponse({ - status: HttpStatus.NO_CONTENT, - description: 'Delete user', - }) - @HttpCode(HttpStatus.NO_CONTENT) - async delete( - @Req() request: AuthenticatedRequest, - ): Promise<{ massage: string }> { - await this.userService.delete(request.user.id); - return { massage: 'User deleted successfully' }; - } + @Delete() + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Delete user', + }) + @HttpCode(HttpStatus.NO_CONTENT) + async delete( + @Req() request: AuthenticatedRequest, + ): Promise<{ massage: string }> { + await this.userService.delete(request.user.id); + return { massage: 'User deleted successfully' }; + } } diff --git a/src/user/user.module.ts b/src/user/user.module.ts index fa96c35..d1a24ae 100644 --- a/src/user/user.module.ts +++ b/src/user/user.module.ts @@ -1,18 +1,13 @@ -import { Module } from "@nestjs/common"; -import { UserController } from "./user.controller"; -import { UserService } from "./user.service"; -import { userProviders } from "./user.providers"; -import { DatabaseModule } from "src/database/database.module"; +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { UserController } from './user.controller'; +import { User } from './user.entity'; +import { UserService } from './user.service'; @Module({ - imports: [ - DatabaseModule, - ], - controllers: [UserController], - providers: [ - ...userProviders, - UserService - ], - exports: [UserService], + imports: [TypeOrmModule.forFeature([User])], + controllers: [UserController], + providers: [UserService], + exports: [UserService], }) -export class UserModule { } \ No newline at end of file +export class UserModule {} diff --git a/src/user/user.service.ts b/src/user/user.service.ts index e720f39..053eefc 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,56 +1,75 @@ -import { Injectable, Inject, NotFoundException, BadRequestException } from "@nestjs/common"; -import { Repository } from "typeorm"; -import { User } from "./user.entity"; -import { UpdateUserDto } from "./dtos/update-user.dto"; -import { FindOneOptions } from "typeorm"; -import { CreateUserDto } from "./dtos/create-user.dto"; -import { hash } from "argon2"; +import { + BadRequestException, + Inject, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { hash } from 'argon2'; +import { createPagination } from 'src/shared/pagination'; +import { FindOneOptions, ILike, Repository } from 'typeorm'; +import { CreateUserDto } from './dtos/create-user.dto'; +import { UpdateUserDto } from './dtos/update-user.dto'; +import { PaginatedUserResponseDto } from './dtos/user-response.dto'; +import { User } from './user.entity'; @Injectable() export class UserService { - constructor( - @Inject("UserRepository") - private readonly userRepository: Repository, - ) { } + constructor( + @Inject('UserRepository') + private readonly userRepository: Repository, + ) {} - async findAll(): Promise { - return this.userRepository.find(); - } + async findAll({ + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }): Promise { + const { find } = await createPagination(this.userRepository, { + page, + limit, + }); - async findOne(options: FindOneOptions): Promise { - const user = this.userRepository.findOne(options); - if (!user) - throw new NotFoundException("User not found"); - return user; - } + const users = await find({ + where: { email: ILike(`%${search}%`) }, + }).run(); + + return users; + } + + async findOne(options: FindOneOptions): Promise { + const user = this.userRepository.findOne(options); + if (!user) throw new NotFoundException('User not found'); + return user; + } - async create(createUserDto: CreateUserDto): Promise { - try { - return this.userRepository.save(createUserDto); - } catch (error) { - if (error instanceof Error) - throw new BadRequestException(error.message); - } + async create(createUserDto: CreateUserDto): Promise { + try { + return this.userRepository.save(createUserDto); + } catch (error) { + if (error instanceof Error) throw new BadRequestException(error.message); } + } - async update(id: string, updateUserDto: UpdateUserDto): Promise { - try { - if (updateUserDto.password) - updateUserDto.password = await hash(updateUserDto.password); - await this.userRepository.update(id, updateUserDto); - return await this.findOne({ where: { id } }); - } catch (error) { - if (error instanceof Error) - throw new NotFoundException("User not found"); - } + async update(id: string, updateUserDto: UpdateUserDto): Promise { + try { + if (updateUserDto.password) + updateUserDto.password = await hash(updateUserDto.password); + await this.userRepository.update(id, updateUserDto); + return await this.findOne({ where: { id } }); + } catch (error) { + if (error instanceof Error) throw new NotFoundException('User not found'); } + } - async delete(id: string): Promise { - try { - await this.userRepository.delete(id); - } catch (error) { - if (error instanceof Error) - throw new NotFoundException("User not found"); - } + async delete(id: string): Promise { + try { + await this.userRepository.delete(id); + } catch (error) { + if (error instanceof Error) throw new NotFoundException('User not found'); } -} \ No newline at end of file + } +} From b561bdfa0082fe9c5913e7460afe81e6f0275595 Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Wed, 13 Nov 2024 17:35:42 +0700 Subject: [PATCH 027/155] feat: Enhance course management with role-based access and teacher association --- src/course/course.controller.ts | 17 +++-- src/course/course.entity.ts | 8 +- src/course/course.service.ts | 105 ++++++++++++++++++++++----- src/course/dtos/create-course.dto.ts | 16 +--- 4 files changed, 103 insertions(+), 43 deletions(-) diff --git a/src/course/course.controller.ts b/src/course/course.controller.ts index bf1943f..3ea7001 100644 --- a/src/course/course.controller.ts +++ b/src/course/course.controller.ts @@ -5,7 +5,6 @@ import { CourseResponseDto, CreateCourseDto, UpdateCourseDto } from "./dtos/inde import { AuthenticatedRequest } from "src/auth/interfaces/authenticated-request.interface"; import { Roles } from "src/shared/decorators/role.decorator"; import { Role } from "src/shared/enums"; -import { UserResponseDto } from "src/user/dtos/user-response.dto"; @Controller("course") @ApiTags("Course") @@ -38,6 +37,7 @@ export class CourseController { description: 'Get course by id', }) async findOne( + @Req() request: AuthenticatedRequest, @Param( 'id', new ParseUUIDPipe({ @@ -47,7 +47,7 @@ export class CourseController { ) id: string, ): Promise { - const course = await this.courseService.findOne({ where: { id } }); + const course = await this.courseService.findOne(request.user.id,request.user.role,{ where: { id } }); return new CourseResponseDto(course); } @@ -60,14 +60,15 @@ export class CourseController { description: 'Create course', }) async create( + @Req() request: AuthenticatedRequest, @Body() createCourseDto: CreateCourseDto, ): Promise { - const course = await this.courseService.create(createCourseDto); + const course = await this.courseService.create(request.user.id,createCourseDto); return new CourseResponseDto(course); } @Patch(':id') - @Roles(Role.TEACHER) + @Roles(Role.TEACHER, Role.ADMIN) @ApiParam({ name: 'id', type: String, @@ -75,7 +76,7 @@ export class CourseController { }) @ApiResponse({ status: HttpStatus.OK, - type: UserResponseDto, + type: CourseResponseDto, description: 'Update course by id', }) async update( @@ -91,13 +92,13 @@ export class CourseController { id: string, ): Promise { - const course = await this.courseService.update(id,request.user.id, updateCourseDto); + const course = await this.courseService.update(id,request.user.id,request.user.role, updateCourseDto); return new CourseResponseDto(course); } @Delete(':id') - @Roles(Role.TEACHER) + @Roles(Role.TEACHER, Role.ADMIN) @ApiParam({ name: 'id', type: String, @@ -118,6 +119,6 @@ export class CourseController { ) id: string, ): Promise { - await this.courseService.delete(id,request.user.id); + await this.courseService.delete(id,request.user.id, request.user.role); } } \ No newline at end of file diff --git a/src/course/course.entity.ts b/src/course/course.entity.ts index 676eebe..33f9270 100644 --- a/src/course/course.entity.ts +++ b/src/course/course.entity.ts @@ -1,4 +1,4 @@ -import { Entity, JoinColumn, ManyToOne } from "typeorm"; +import { Entity, ManyToOne } from "typeorm"; import { Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"; import { User } from "src/user/user.entity"; import { CourseLevel } from "src/shared/enums/course-level.enum"; @@ -20,11 +20,7 @@ export class Course { }) description: string; - @Column({ name: 'teacher_id' }) - teacherId: string; - - @ManyToOne(() => User) - @JoinColumn({ name: 'teacher_id' }) + @ManyToOne(() => User, user => user.courses) teacher: User; @Column({ diff --git a/src/course/course.service.ts b/src/course/course.service.ts index 9a0cf28..d1db65b 100644 --- a/src/course/course.service.ts +++ b/src/course/course.service.ts @@ -1,7 +1,8 @@ import { BadRequestException, Inject, Injectable, NotFoundException } from "@nestjs/common"; -import { FindOneOptions, Repository } from "typeorm"; +import { FindOneOptions, FindOptionsWhere, Repository } from "typeorm"; import { Course } from "./course.entity"; import { UpdateCourseDto,CreateCourseDto } from "./dtos/index"; +import { CourseStatus, Role } from "src/shared/enums"; @@ -13,39 +14,106 @@ export class CourseService { ) { } async findAll(): Promise { - return this.courseRepository.find(); + return this.courseRepository.find({ + relations: { + teacher: true, + }, + }); } - async findOne(options: FindOneOptions): Promise { - const course = this.courseRepository.findOne(options); - if (!course) + async findOne( + userId: string, + role: Role, + options: FindOneOptions + ): Promise { + switch (role) { + case Role.STUDENT: + options.where = { + ...(options.where as FindOptionsWhere), + status: CourseStatus.PUBLISHED + }; + break; + + case Role.TEACHER: + options.where = [ + { + ...(options.where as FindOptionsWhere), + status: CourseStatus.PUBLISHED + }, + { + ...(options.where as FindOptionsWhere), + teacher: { id: userId } + } + ]; + break; + + case Role.ADMIN: + break; + + default: + throw new BadRequestException("Invalid role"); + } + + const course = await this.courseRepository.findOne({ + where: options.where, + relations: { + teacher: true + } + }); + + if (!course) { throw new NotFoundException("Course not found"); + } + return course; } - async create(createCourseDto: CreateCourseDto): Promise { + async create(userId: string, createCourseDto: CreateCourseDto): Promise { try { - return this.courseRepository.save(createCourseDto); + return this.courseRepository.save({ ...createCourseDto, teacher: { id: userId } + }); } catch (error) { if (error instanceof Error) throw new BadRequestException(error.message); } } - async update(id: string,userId: string, updateCourseDto: UpdateCourseDto): Promise { + async update(id: string,userId: string,role: Role, updateCourseDto: UpdateCourseDto): Promise { + const status = await this.checkCourseStatus(id); + + + switch (role) { + case Role.TEACHER: + await this.checkOwnership(id, userId); + break; + case Role.ADMIN: + if (status !== CourseStatus.DRAFT) + throw new BadRequestException("Only draft courses can be updated by an admin"); + default: + throw new BadRequestException("Invalid role"); + } + + + if (status !== CourseStatus.DRAFT && updateCourseDto.status === CourseStatus.DRAFT) + throw new BadRequestException("You can't update the status of a published or archived course to draft"); + try { - await this.checkOwnership(id, userId); await this.courseRepository.update(id, updateCourseDto); - return await this.findOne({ where: { id } }); + return await this.courseRepository.findOne({ where: { id }, + relations : { + teacher: true, + } + }); } catch (error) { if (error instanceof Error) throw new NotFoundException("Course not found"); } } - async delete(id: string, userId : string): Promise { - try { + async delete(id: string, userId : string ,role: Role): Promise { + if (role === Role.TEACHER) await this.checkOwnership(id, userId); + try { await this.courseRepository.delete(id); } catch (error) { if (error instanceof Error) @@ -53,10 +121,13 @@ export class CourseService { } } - private async checkOwnership(id: string, teacherId: string): Promise { - const course = await this.findOne({ where: { id } }); - if (course.teacher.id !== teacherId) + private async checkOwnership(id: string, userId: string): Promise { + const course = await this.courseRepository.findOne({ where: { id } }); + if (course.teacher.id !== userId) throw new BadRequestException("You don't own this course"); } - -} \ No newline at end of file + private async checkCourseStatus(id: string): Promise { + const course = await this.courseRepository.findOne({ where: { id } }); + return course.status; + } +} diff --git a/src/course/dtos/create-course.dto.ts b/src/course/dtos/create-course.dto.ts index 4bd6780..c1a946f 100644 --- a/src/course/dtos/create-course.dto.ts +++ b/src/course/dtos/create-course.dto.ts @@ -18,14 +18,6 @@ export class CreateCourseDto { example: 'This course is an introduction to programming', }) description: string; - @IsNotEmpty() - @ApiProperty({ - description: 'Teacher ID', - type: String, - example: '123456', - }) - teacherId: string; - @IsNotEmpty() @ApiProperty({ description: 'Course thumbnail', @@ -47,8 +39,8 @@ export class CreateCourseDto { @ApiProperty({ description: 'Course level', type: String, - example: 'beginner', - enum: ['beginner', 'intermediate', 'advanced'], + example: CourseLevel.BEGINNER, + enum: CourseLevel, }) level: CourseLevel; @@ -66,8 +58,8 @@ export class CreateCourseDto { @ApiProperty({ description: 'Course status', type: String, - example: 'draft', - enum: ['draft', 'published', 'archived'], + example: CourseStatus.DRAFT, + enum: CourseStatus, }) status: CourseStatus; } \ No newline at end of file From e1ddd5b13eb5492a41ea90a5ca6d53f1e557ca0e Mon Sep 17 00:00:00 2001 From: khris-xp Date: Thu, 14 Nov 2024 12:31:24 +0700 Subject: [PATCH 028/155] feat: course module service with reordering --- src/app.module.ts | 29 +- src/course-module/course-module.controller.ts | 159 +++++++++ src/course-module/course-module.entity.ts | 56 +++ src/course-module/course-module.module.ts | 14 + src/course-module/course-module.provider.ts | 11 + src/course-module/course-module.service.ts | 191 ++++++++++ .../dtos/course-module-response.dto.ts | 86 +++++ .../dtos/create-course-module.dto.ts | 36 ++ .../dtos/update-course-module.dto.ts | 4 + src/course/course.controller.ts | 329 ++++++++++-------- src/course/course.entity.ts | 147 ++++---- src/course/dtos/course-response.dto.ts | 282 +++++++-------- src/shared/configs/database.config.ts | 3 +- 13 files changed, 967 insertions(+), 380 deletions(-) create mode 100644 src/course-module/course-module.controller.ts create mode 100644 src/course-module/course-module.entity.ts create mode 100644 src/course-module/course-module.module.ts create mode 100644 src/course-module/course-module.provider.ts create mode 100644 src/course-module/course-module.service.ts create mode 100644 src/course-module/dtos/course-module-response.dto.ts create mode 100644 src/course-module/dtos/create-course-module.dto.ts create mode 100644 src/course-module/dtos/update-course-module.dto.ts diff --git a/src/app.module.ts b/src/app.module.ts index a6f30bb..64a4c20 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,25 +1,25 @@ import { Module } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { APP_GUARD } from '@nestjs/core'; +import { JwtModule } from '@nestjs/jwt'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { AppController } from './app.controller'; import { AppService } from './app.service'; +import { AuthGuard } from './auth/auth.guard'; import { AuthModule } from './auth/auth.module'; -import { ConfigModule } from '@nestjs/config'; -import { dotenvConfig } from './shared/configs/dotenv.config'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { ConfigService } from '@nestjs/config'; +import { CategoryModule } from './category/category.module'; +import { CourseModuleModule } from './course-module/course-module.module'; +import { Course } from './course/course.entity'; +import { CourseModule } from './course/course.module'; +import { DatabaseModule } from './database/database.module'; import { databaseConfig } from './shared/configs/database.config'; +import { dotenvConfig } from './shared/configs/dotenv.config'; import { GLOBAL_CONFIG } from './shared/constants/global-config.constant'; -import { JwtModule } from '@nestjs/jwt'; -import { DatabaseModule } from './database/database.module'; -import { UserModule } from './user/user.module'; -import { User } from './user/user.entity'; -import { APP_GUARD } from '@nestjs/core'; -import { AuthGuard } from './auth/auth.guard'; +import { RolesGuard } from './shared/guards/role.guard'; import { UserStreak } from './user-streak/user-streak.entity'; import { UserStreakModule } from './user-streak/user-streak.module'; -import { RolesGuard } from './shared/guards/role.guard'; -import { CategoryModule } from './category/category.module'; -import { CourseModule } from './course/course.module'; -import { Course } from './course/course.entity'; +import { User } from './user/user.entity'; +import { UserModule } from './user/user.module'; const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); @@ -49,6 +49,7 @@ const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); UserStreakModule, CategoryModule, CourseModule, + CourseModuleModule, ], controllers: [AppController], providers: [ diff --git a/src/course-module/course-module.controller.ts b/src/course-module/course-module.controller.ts new file mode 100644 index 0000000..2de8cc3 --- /dev/null +++ b/src/course-module/course-module.controller.ts @@ -0,0 +1,159 @@ +import { + BadRequestException, + Body, + Controller, + Delete, + Get, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, +} from '@nestjs/common'; +import { + ApiBearerAuth, + ApiParam, + ApiQuery, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { CourseModuleService } from './course-module.service'; +import { + CourseModuleResponseDto, + PaginatedCourseModuleResponseDto, +} from './dtos/course-module-response.dto'; +import { CreateCourseModuleDto } from './dtos/create-course-module.dto'; +import { UpdateCourseModuleDto } from './dtos/update-course-module.dto'; + +@Controller('course-module') +@ApiTags('Course Modules') +@ApiBearerAuth() +@Injectable() +export class CourseModuleController { + constructor(private readonly courseModuleService: CourseModuleService) {} + + @Get() + @ApiResponse({ + status: HttpStatus.OK, + type: CourseModuleResponseDto, + description: 'Get all course modules', + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findAll( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return this.courseModuleService.findAll({ + page: query.page, + limit: query.limit, + search: query.search, + }); + } + + @Get(':id') + @ApiParam({ + name: 'id', + type: String, + description: 'Course Module ID', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: CourseModuleResponseDto, + description: 'Get a course module', + }) + async findOne( + @Param('id', new ParseUUIDPipe()) id: string, + ): Promise { + return this.courseModuleService.findOne(id, { + where: { id }, + }); + } + + @Get('course/:courseId') + @ApiParam({ + name: 'courseId', + type: String, + description: 'Course ID', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: CourseModuleResponseDto, + description: 'Get course modules by course ID', + isArray: true, + }) + async findByCourseId( + @Param('courseId', new ParseUUIDPipe()) courseId: string, + ): Promise { + return this.courseModuleService.findByCourseId(courseId); + } + + @Post() + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.CREATED, + type: CourseModuleResponseDto, + description: 'Create a course module', + }) + async create( + @Body() createCourseModuleDto: CreateCourseModuleDto, + ): Promise { + return this.courseModuleService.create(createCourseModuleDto); + } + + @Patch(':id') + @Roles(Role.TEACHER) + @ApiParam({ + name: 'id', + type: String, + description: 'Course Module ID', + }) + async update( + @Param('id', new ParseUUIDPipe()) id: string, + @Body() updateCourseModuleDto: UpdateCourseModuleDto, + ): Promise { + return this.courseModuleService.update(id, updateCourseModuleDto); + } + + @Delete(':id') + @Roles(Role.TEACHER) + @ApiParam({ + name: 'id', + type: String, + description: 'Course Module ID', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: CourseModuleResponseDto, + description: 'Delete a course module', + }) + async remove( + @Param('id', new ParseUUIDPipe()) id: string, + ): Promise { + return this.courseModuleService.remove(id); + } +} diff --git a/src/course-module/course-module.entity.ts b/src/course-module/course-module.entity.ts new file mode 100644 index 0000000..7f52588 --- /dev/null +++ b/src/course-module/course-module.entity.ts @@ -0,0 +1,56 @@ +import { Course } from 'src/course/course.entity'; +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +@Entity() +export class CourseModule { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ + type: String, + nullable: false, + }) + title: string; + + @Column({ + type: String, + nullable: false, + }) + description: string; + + @Column({ + type: Number, + nullable: false, + }) + orderIndex: number; + + @ManyToOne(() => Course, (course) => course.modules, { + onDelete: 'CASCADE', + nullable: false, + }) + @JoinColumn({ name: 'course_id' }) + course: Course; + + @Column({ name: 'course_id' }) + courseId: string; + + @CreateDateColumn({ + type: 'timestamp', + name: 'created_at', + }) + createdAt: Date; + + @UpdateDateColumn({ + type: 'timestamp', + name: 'updated_at', + }) + updatedAt: Date; +} diff --git a/src/course-module/course-module.module.ts b/src/course-module/course-module.module.ts new file mode 100644 index 0000000..be25356 --- /dev/null +++ b/src/course-module/course-module.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { CourseModule } from 'src/course/course.module'; +import { DatabaseModule } from 'src/database/database.module'; +import { CourseModuleController } from './course-module.controller'; +import { courseModuleProviders } from './course-module.provider'; +import { CourseModuleService } from './course-module.service'; + +@Module({ + imports: [DatabaseModule, CourseModule], + controllers: [CourseModuleController], + providers: [...courseModuleProviders, CourseModuleService], + exports: [CourseModuleService], +}) +export class CourseModuleModule {} diff --git a/src/course-module/course-module.provider.ts b/src/course-module/course-module.provider.ts new file mode 100644 index 0000000..b011ea0 --- /dev/null +++ b/src/course-module/course-module.provider.ts @@ -0,0 +1,11 @@ +import { DataSource } from 'typeorm'; +import { CourseModule } from './course-module.entity'; + +export const courseModuleProviders = [ + { + provide: 'CourseModuleRepository', + useFactory: (dataSource: DataSource) => + dataSource.getRepository(CourseModule), + inject: ['DataSource'], + }, +]; diff --git a/src/course-module/course-module.service.ts b/src/course-module/course-module.service.ts new file mode 100644 index 0000000..e7fbb1f --- /dev/null +++ b/src/course-module/course-module.service.ts @@ -0,0 +1,191 @@ +import { + BadRequestException, + Inject, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { createPagination } from 'src/shared/pagination'; +import { FindOneOptions, FindOptionsWhere, ILike, Repository } from 'typeorm'; +import { CourseModule } from './course-module.entity'; +import { PaginatedCourseModuleResponseDto } from './dtos/course-module-response.dto'; +import { CreateCourseModuleDto } from './dtos/create-course-module.dto'; +import { UpdateCourseModuleDto } from './dtos/update-course-module.dto'; + +@Injectable() +export class CourseModuleService { + constructor( + @Inject('CourseModuleRepository') + private readonly courseModuleRepository: Repository, + ) {} + + async findAll({ + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }): Promise { + const { find } = await createPagination(this.courseModuleRepository, { + page, + limit, + }); + + const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; + const whereCondition = { ...baseSearch }; + + const courseModules = await find({ + where: whereCondition, + relations: { + course: true, + }, + }).run(); + + return courseModules; + } + + async findOne( + id: string, + options: FindOneOptions, + ): Promise { + const baseWhere = options.where as FindOptionsWhere; + const whereCondition = { ...baseWhere, id }; + + const courseModule = await this.courseModuleRepository.findOne({ + where: whereCondition, + relations: { + course: true, + }, + }); + + if (!courseModule) { + throw new NotFoundException('Course Module not found'); + } + + return courseModule; + } + + async findByCourseId(courseId: string): Promise { + const courseModules = await this.courseModuleRepository.find({ + where: { courseId }, + relations: { + course: true, + }, + }); + + return courseModules; + } + + async validateAndGetNextOrderIndex(courseId: string): Promise { + const existingModules = await this.courseModuleRepository.find({ + where: { courseId }, + order: { orderIndex: 'DESC' }, + }); + + const orderIndices = existingModules.map((module) => module.orderIndex); + const hasDuplicates = new Set(orderIndices).size !== orderIndices.length; + + if (hasDuplicates) { + throw new BadRequestException( + 'Duplicate orderIndex values detected in course modules', + ); + } + + return existingModules.length > 0 ? existingModules[0].orderIndex + 1 : 1; + } + + async create( + createCourseModuleDto: CreateCourseModuleDto, + ): Promise { + if (!createCourseModuleDto.orderIndex) { + createCourseModuleDto.orderIndex = + await this.validateAndGetNextOrderIndex(createCourseModuleDto.courseId); + } else { + const existingModule = await this.courseModuleRepository.findOne({ + where: { + courseId: createCourseModuleDto.courseId, + orderIndex: createCourseModuleDto.orderIndex, + }, + }); + + if (existingModule) { + throw new BadRequestException( + `Module with orderIndex ${createCourseModuleDto.orderIndex} already exists in this course`, + ); + } + } + + const courseModule = this.courseModuleRepository.create( + createCourseModuleDto, + ); + await this.courseModuleRepository.save(courseModule); + + return courseModule; + } + + async reorderModules(courseId: string, startIndex: number): Promise { + const modulesToReorder = await this.courseModuleRepository.find({ + where: { courseId }, + order: { orderIndex: 'ASC' }, + }); + + for (let i = 0; i < modulesToReorder.length; i++) { + modulesToReorder[i].orderIndex = i + 1; + } + + await this.courseModuleRepository.save(modulesToReorder); + } + + async update( + id: string, + updateCourseModuleDto: UpdateCourseModuleDto, + ): Promise { + const courseModule = await this.courseModuleRepository.findOne({ + where: { id }, + }); + + if (!courseModule) { + throw new BadRequestException('Course module not found'); + } + + if ( + updateCourseModuleDto.orderIndex && + updateCourseModuleDto.orderIndex !== courseModule.orderIndex + ) { + const existingModule = await this.courseModuleRepository.findOne({ + where: { + courseId: courseModule.courseId, + orderIndex: updateCourseModuleDto.orderIndex, + }, + }); + + if (existingModule) { + throw new BadRequestException( + `Module with orderIndex ${updateCourseModuleDto.orderIndex} already exists in this course`, + ); + } + } + + Object.assign(courseModule, updateCourseModuleDto); + await this.courseModuleRepository.save(courseModule); + + return courseModule; + } + + async remove(id: string): Promise { + const courseModule = await this.courseModuleRepository.findOne({ + where: { id }, + }); + + if (!courseModule) { + throw new BadRequestException('Course module not found'); + } + + const result = await this.courseModuleRepository.remove(courseModule); + + await this.reorderModules(courseModule.courseId, courseModule.orderIndex); + + return result; + } +} diff --git a/src/course-module/dtos/course-module-response.dto.ts b/src/course-module/dtos/course-module-response.dto.ts new file mode 100644 index 0000000..f8b0292 --- /dev/null +++ b/src/course-module/dtos/course-module-response.dto.ts @@ -0,0 +1,86 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { CourseResponseDto } from 'src/course/dtos'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { CourseModule } from '../course-module.entity'; + +export class CourseModuleResponseDto { + @ApiProperty({ + description: 'Course Module ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + id: string; + + @ApiProperty({ + description: 'Course Module title', + type: String, + example: 'Introduction to Variables', + }) + title: string; + + @ApiProperty({ + description: 'Course Module description', + type: String, + example: 'Learn about variables and data types in programming', + }) + description: string; + + @ApiProperty({ + description: 'Course Data', + type: CourseResponseDto, + example: { + id: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + title: 'Introduction to Programming', + description: 'This course is an introduction to programming', + thumbnail: 'https://www.example.com/thumbnail.jpg', + duration: 60, + level: 'BEGINNER', + price: 100, + status: 'DRAFT', + createdAt: new Date(), + updatedAt: new Date(), + }, + }) + course: CourseResponseDto; + + @ApiProperty({ + description: 'Course Module created date', + type: Date, + example: new Date(), + }) + createdAt: Date; + + @ApiProperty({ + description: 'Course Module updated date', + type: Date, + example: new Date(), + }) + updatedAt: Date; + + constructor(courseModule: CourseModule) { + this.id = courseModule.id; + this.title = courseModule.title; + this.description = courseModule.description; + this.course = courseModule.course + ? new CourseResponseDto(courseModule.course) + : null; + this.createdAt = courseModule.createdAt; + this.updatedAt = courseModule.updatedAt; + } +} + +export class PaginatedCourseModuleResponseDto extends PaginatedResponse( + CourseModuleResponseDto, +) { + constructor( + courseModules: CourseModule[], + total: number, + pageSize: number, + currentPage: number, + ) { + const courseModuleDtos = courseModules.map( + (courseModule) => new CourseModuleResponseDto(courseModule), + ); + super(courseModuleDtos, total, pageSize, currentPage); + } +} diff --git a/src/course-module/dtos/create-course-module.dto.ts b/src/course-module/dtos/create-course-module.dto.ts new file mode 100644 index 0000000..36407b8 --- /dev/null +++ b/src/course-module/dtos/create-course-module.dto.ts @@ -0,0 +1,36 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty } from 'class-validator'; + +export class CreateCourseModuleDto { + @IsNotEmpty() + @ApiProperty({ + description: 'Course Module title', + type: String, + example: 'Introduction to Programming', + }) + title: string; + + @IsNotEmpty() + @ApiProperty({ + description: 'Course Module description', + type: String, + example: 'This module is an introduction to programming', + }) + description: string; + + @IsNotEmpty() + @ApiProperty({ + description: 'Course Module order index', + type: Number, + example: 1, + }) + orderIndex: number; + + @IsNotEmpty() + @ApiProperty({ + description: 'Course ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + courseId: string; +} diff --git a/src/course-module/dtos/update-course-module.dto.ts b/src/course-module/dtos/update-course-module.dto.ts new file mode 100644 index 0000000..33898ed --- /dev/null +++ b/src/course-module/dtos/update-course-module.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateCourseModuleDto } from './create-course-module.dto'; + +export class UpdateCourseModuleDto extends PartialType(CreateCourseModuleDto) {} diff --git a/src/course/course.controller.ts b/src/course/course.controller.ts index 96fd692..7a82bd1 100644 --- a/src/course/course.controller.ts +++ b/src/course/course.controller.ts @@ -1,172 +1,205 @@ -import { BadRequestException, Body, Controller, Delete, Get, HttpStatus, Injectable, Param, ParseUUIDPipe, Patch, Post, Query, Req } from "@nestjs/common"; -import { ApiBearerAuth, ApiParam, ApiQuery, ApiResponse, ApiTags } from "@nestjs/swagger"; -import { CourseService } from "./course.service"; -import { CourseResponseDto, CreateCourseDto, PaginatedCourseResponeDto, UpdateCourseDto } from "./dtos/index"; -import { AuthenticatedRequest } from "src/auth/interfaces/authenticated-request.interface"; -import { Roles } from "src/shared/decorators/role.decorator"; -import { Role } from "src/shared/enums"; -import { PaginateQueryDto } from "src/shared/pagination/dtos/paginate-query.dto"; -import { CategoryService } from "src/category/category.service"; +import { + BadRequestException, + Body, + Controller, + Delete, + Get, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, +} from '@nestjs/common'; +import { + ApiBearerAuth, + ApiParam, + ApiQuery, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { CategoryService } from 'src/category/category.service'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { CourseService } from './course.service'; +import { + CourseResponseDto, + CreateCourseDto, + PaginatedCourseResponeDto, + UpdateCourseDto, +} from './dtos/index'; -@Controller("course") -@ApiTags("Course") +@Controller('course') +@ApiTags('Course') @ApiBearerAuth() @Injectable() export class CourseController { - constructor(private readonly courseService : CourseService, private readonly categoryService : CategoryService) { } - - @Get() - @ApiResponse({ - status: HttpStatus.OK, - type: CourseResponseDto, - description: 'Get all course', - isArray: true, - }) - @ApiQuery({ - name: 'page', - type: Number, - required: false, - description: 'Page number', - }) - @ApiQuery({ + constructor( + private readonly courseService: CourseService, + private readonly categoryService: CategoryService, + ) {} + @Get() + @ApiResponse({ + status: HttpStatus.OK, + type: CourseResponseDto, + description: 'Get all course', + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ name: 'limit', type: Number, required: false, description: 'Items per page', - }) - @ApiQuery({ + }) + @ApiQuery({ name: 'search', type: String, required: false, description: 'Search by email', - }) - async findAll( + }) + async findAll( @Req() request: AuthenticatedRequest, @Query() query: PaginateQueryDto, - ): Promise { - return this.courseService.findAll({ - page: query.page, - limit: query.limit, - search: query.search, - userId: request.user.id, - role: request.user.role, - }); - } - - @Get(':id') - @ApiParam({ - name: 'id', - type: String, - description: 'Course id', - }) - @ApiResponse({ - status: HttpStatus.OK, - type: CourseResponseDto, - description: 'Get course by id', - }) - async findOne( - @Req() request: AuthenticatedRequest, - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - ): Promise { - const course = await this.courseService.findOne(request.user.id,request.user.role,{ where: { id } }); - return new CourseResponseDto(course); - } + ): Promise { + return this.courseService.findAll({ + page: query.page, + limit: query.limit, + search: query.search, + userId: request.user.id, + role: request.user.role, + }); + } + @Get(':id') + @ApiParam({ + name: 'id', + type: String, + description: 'Course id', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: CourseResponseDto, + description: 'Get course by id', + }) + async findOne( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const course = await this.courseService.findOne( + request.user.id, + request.user.role, + { where: { id } }, + ); + return new CourseResponseDto(course); + } - @Post() - @Roles(Role.TEACHER) - @ApiResponse({ - status: HttpStatus.CREATED, - type: CourseResponseDto, - description: 'Create course', - }) - async create( - @Req() request: AuthenticatedRequest, - @Body() createCourseDto: CreateCourseDto, - ) { - const category = await this.categoryService.findOne({ where: { id: createCourseDto.categoryId } }); - - if (!category) { - throw new BadRequestException('Category not found'); - } - const course = await this.courseService.create(request.user.id,createCourseDto); + @Post() + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.CREATED, + type: CourseResponseDto, + description: 'Create course', + }) + async create( + @Req() request: AuthenticatedRequest, + @Body() createCourseDto: CreateCourseDto, + ) { + const category = await this.categoryService.findOne({ + where: { id: createCourseDto.categoryId }, + }); - return course + if (!category) { + throw new BadRequestException('Category not found'); } + const course = await this.courseService.create( + request.user.id, + createCourseDto, + ); - @Patch(':id') - @Roles(Role.TEACHER, Role.ADMIN) - @ApiParam({ - name: 'id', - type: String, - description: 'Course id', - }) - @ApiResponse({ - status: HttpStatus.OK, - type: CourseResponseDto, - description: 'Update course by id', - }) - async update( - @Req() request: AuthenticatedRequest, - @Body() updateCourseDto:UpdateCourseDto, - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, + return course; + } - ): Promise { - const category = await this.categoryService.findOne({ - where: { id: updateCourseDto.categoryId } - }); - - if (!category) { - throw new BadRequestException('Category not found'); - } - - const course = await this.courseService.update( - id, - request.user.id, - request.user.role, - updateCourseDto - ); + @Patch(':id') + @Roles(Role.TEACHER, Role.ADMIN) + @ApiParam({ + name: 'id', + type: String, + description: 'Course id', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: CourseResponseDto, + description: 'Update course by id', + }) + async update( + @Req() request: AuthenticatedRequest, + @Body() updateCourseDto: UpdateCourseDto, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const category = await this.categoryService.findOne({ + where: { id: updateCourseDto.categoryId }, + }); - return new CourseResponseDto(course); + if (!category) { + throw new BadRequestException('Category not found'); } + const course = await this.courseService.update( + id, + request.user.id, + request.user.role, + updateCourseDto, + ); - @Delete(':id') - @Roles(Role.TEACHER, Role.ADMIN) - @ApiParam({ - name: 'id', - type: String, - description: 'Course id', - }) - @ApiResponse({ - status: HttpStatus.OK, - description: 'Delete course by id', - }) - async delete( - @Req() request: AuthenticatedRequest, - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - ): Promise { - await this.courseService.delete(id,request.user.id, request.user.role); - } -} \ No newline at end of file + return new CourseResponseDto(course); + } + + @Delete(':id') + @Roles(Role.TEACHER, Role.ADMIN) + @ApiParam({ + name: 'id', + type: String, + description: 'Course id', + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Delete course by id', + }) + async delete( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + await this.courseService.delete(id, request.user.id, request.user.role); + } +} diff --git a/src/course/course.entity.ts b/src/course/course.entity.ts index 26427ab..246dab9 100644 --- a/src/course/course.entity.ts +++ b/src/course/course.entity.ts @@ -1,77 +1,90 @@ -import { Entity, JoinColumn, ManyToOne } from "typeorm"; -import { Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"; -import { User } from "src/user/user.entity"; -import { CourseLevel } from "src/shared/enums/course-level.enum"; -import { CourseStatus } from "src/shared/enums/course-status.enum"; -import { Category } from "src/category/category.entity"; - +import { Category } from 'src/category/category.entity'; +import { CourseModule } from 'src/course-module/course-module.entity'; +import { CourseLevel } from 'src/shared/enums/course-level.enum'; +import { CourseStatus } from 'src/shared/enums/course-status.enum'; +import { User } from 'src/user/user.entity'; +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + OneToMany, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; @Entity() export class Course { - @PrimaryGeneratedColumn("uuid") - id: string; - @Column({ - type: String, - nullable: false, - }) - title: string; - @Column({ - type: String, - nullable: false, - }) - description: string; - - @ManyToOne(() => User) - @JoinColumn({ name: 'teacher_id' }) - teacher: User; - - @ManyToOne(() => Category) - @JoinColumn({ name: 'category_id' }) - category: Category; + @PrimaryGeneratedColumn('uuid') + id: string; + @Column({ + type: String, + nullable: false, + }) + title: string; + @Column({ + type: String, + nullable: false, + }) + description: string; + + @ManyToOne(() => User) + @JoinColumn({ name: 'teacher_id' }) + teacher: User; + + @ManyToOne(() => Category) + @JoinColumn({ name: 'category_id' }) + category: Category; + + @OneToMany(() => CourseModule, (courseModule) => courseModule.course, { + cascade: true, + }) + modules: CourseModule[]; - @Column({ - type: String, - nullable: false, - }) - thumbnail: string; + @Column({ + type: String, + nullable: false, + }) + thumbnail: string; - @Column({ - type: Number, - nullable: false, - }) - duration: number; + @Column({ + type: Number, + nullable: false, + }) + duration: number; - @Column({ - type: 'enum', - nullable: false, - enum: CourseLevel, - default: CourseLevel.BEGINNER, - }) - level: CourseLevel; + @Column({ + type: 'enum', + nullable: false, + enum: CourseLevel, + default: CourseLevel.BEGINNER, + }) + level: CourseLevel; - @Column({ - type: Number, - nullable: false, - }) - price: number; + @Column({ + type: Number, + nullable: false, + }) + price: number; - @Column({ - type: 'enum', - nullable: false, - enum: CourseStatus, - default: CourseStatus.DRAFT, - }) - status: CourseStatus; + @Column({ + type: 'enum', + nullable: false, + enum: CourseStatus, + default: CourseStatus.DRAFT, + }) + status: CourseStatus; - @CreateDateColumn({ - type: "timestamp", - nullable: false, - }) - createdAt: Date; + @CreateDateColumn({ + type: 'timestamp', + nullable: false, + }) + createdAt: Date; - @UpdateDateColumn({ - type: "timestamp", - nullable: false, - }) - updatedAt: Date; -} \ No newline at end of file + @UpdateDateColumn({ + type: 'timestamp', + nullable: false, + }) + updatedAt: Date; +} diff --git a/src/course/dtos/course-response.dto.ts b/src/course/dtos/course-response.dto.ts index 14219eb..1727466 100644 --- a/src/course/dtos/course-response.dto.ts +++ b/src/course/dtos/course-response.dto.ts @@ -1,155 +1,137 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { UserResponseDto } from "src/user/dtos/user-response.dto"; -import { Course } from "../course.entity"; -import { CourseLevel, CourseStatus } from "src/shared/enums"; -import { categoryResponseDto } from "src/category/dtos/category-response.dto"; -import { PaginatedResponse } from "src/shared/pagination/dtos/paginate-response.dto"; - - export class CourseResponseDto { - @ApiProperty( - { - description: 'Course ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }) - id: string; - - @ApiProperty( - { - description: 'Course title', - type: String, - example: 'Introduction to Programming', - }) - title: string; - - @ApiProperty( - { - description: 'Course description', - type: String, - example: 'This course is an introduction to programming', - }) - description: string; - - @ApiProperty( - { - description: 'Teacher Data', - type: UserResponseDto, - example: { - id: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - email: 'johndoe@gmail.com', - role: 'teacher', - createdAt: new Date(), - updatedAt: new Date(), - fullname: 'John Doe', - } - }) - teacher: UserResponseDto; - - @ApiProperty( - { - description: 'Category Data', - type: categoryResponseDto, - example: { - id: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - title: 'Programming', - description: 'Programming courses', - slug: 'programming', - createdAt: new Date(), - updatedAt: new Date(), - } - }) - category: categoryResponseDto; - - - @ApiProperty( - { - description: 'Course thumbnail', - type: String, - example: 'https://www.example.com/thumbnail.jpg', - }) - thumbnail: string; - - @ApiProperty( - { - description: 'Course duration', - type: Number, - example: 60, - }) - duration: number; - - @ApiProperty( - { - description: 'Course level', - type: String, - example: CourseLevel.BEGINNER, - enum: CourseLevel, - }) - level: CourseLevel; - - @ApiProperty( - { - description: 'Course price', - type: Number, - example: 100, - }) - price: number; - - @ApiProperty( - { - description: 'Course status', - type: String, - example: CourseStatus.DRAFT, - enum: CourseStatus, - }) - status: CourseStatus; - - @ApiProperty( - { - description: 'Course created date', - type: Date, - example: new Date(), - }) - createdAt: Date; - - @ApiProperty( - { - description: 'Course updated date', - type: Date, - example: new Date(), - }) - updatedAt: Date; - - - constructor(course: Course) { - this.id = course.id; - this.title = course.title; - this.description = course.description; - this.teacher = new UserResponseDto(course.teacher); - this.thumbnail = course.thumbnail; - this.category = new categoryResponseDto(course.category); - this.duration = course.duration; - this.level = course.level; - this.price = course.price; - this.status = course.status; - this.createdAt = course.createdAt; - this.updatedAt = course.updatedAt; - } - +import { ApiProperty } from '@nestjs/swagger'; +import { categoryResponseDto } from 'src/category/dtos/category-response.dto'; +import { CourseLevel, CourseStatus } from 'src/shared/enums'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { UserResponseDto } from 'src/user/dtos/user-response.dto'; +import { Course } from '../course.entity'; + +export class CourseResponseDto { + @ApiProperty({ + description: 'Course ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + id: string; + + @ApiProperty({ + description: 'Course title', + type: String, + example: 'Introduction to Programming', + }) + title: string; + + @ApiProperty({ + description: 'Course description', + type: String, + example: 'This course is an introduction to programming', + }) + description: string; + + @ApiProperty({ + description: 'Teacher Data', + type: UserResponseDto, + example: { + id: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + email: 'johndoe@gmail.com', + role: 'teacher', + createdAt: new Date(), + updatedAt: new Date(), + fullname: 'John Doe', + }, + }) + teacher: UserResponseDto; + + @ApiProperty({ + description: 'Category Data', + type: categoryResponseDto, + example: { + id: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + title: 'Programming', + description: 'Programming courses', + slug: 'programming', + createdAt: new Date(), + updatedAt: new Date(), + }, + }) + category: categoryResponseDto; + + @ApiProperty({ + description: 'Course thumbnail', + type: String, + example: 'https://www.example.com/thumbnail.jpg', + }) + thumbnail: string; + + @ApiProperty({ + description: 'Course duration', + type: Number, + example: 60, + }) + duration: number; + + @ApiProperty({ + description: 'Course level', + type: String, + example: CourseLevel.BEGINNER, + enum: CourseLevel, + }) + level: CourseLevel; + + @ApiProperty({ + description: 'Course price', + type: Number, + example: 100, + }) + price: number; + + @ApiProperty({ + description: 'Course status', + type: String, + example: CourseStatus.DRAFT, + enum: CourseStatus, + }) + status: CourseStatus; + + @ApiProperty({ + description: 'Course created date', + type: Date, + example: new Date(), + }) + createdAt: Date; + + @ApiProperty({ + description: 'Course updated date', + type: Date, + example: new Date(), + }) + updatedAt: Date; + + constructor(course: Course) { + this.id = course.id; + this.title = course.title; + this.description = course.description; + this.teacher = new UserResponseDto(course.teacher); + this.thumbnail = course.thumbnail; + this.category = new categoryResponseDto(course.category); + this.duration = course.duration; + this.level = course.level; + this.price = course.price; + this.status = course.status; + this.createdAt = course.createdAt; + this.updatedAt = course.updatedAt; + } } - export class PaginatedCourseResponeDto extends PaginatedResponse( - CourseResponseDto, + CourseResponseDto, ) { - constructor( - courses: Course[], - total: number, - pageSize: number, - currentPage: number - ) { - const courseDtos = courses.map((course) => new CourseResponseDto(course)); - super(courseDtos, total, pageSize, currentPage); - } - + constructor( + courses: Course[], + total: number, + pageSize: number, + currentPage: number, + ) { + const courseDtos = courses.map((course) => new CourseResponseDto(course)); + super(courseDtos, total, pageSize, currentPage); + } } - diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index 00906ae..9193ce6 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -6,6 +6,7 @@ import { User } from 'src/user/user.entity'; import { Category } from 'src/category/category.entity'; import { UserStreak } from 'src/user-streak/user-streak.entity'; import { Course } from "src/course/course.entity"; +import { CourseModule } from 'src/course-module/course-module.entity'; const configService = new ConfigService(); @@ -17,7 +18,7 @@ export const databaseConfig: DataSourceOptions = { password: configService.get(GLOBAL_CONFIG.DB_PASSWORD), database: configService.get(GLOBAL_CONFIG.DB_DATABASE), logging: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), - entities: [User, UserStreak, Category, Course], + entities: [User, UserStreak, Category, Course, CourseModule], }; export default new DataSource(databaseConfig); From b6ce526cf615205dffd4fc0794c81173af3a1f33 Mon Sep 17 00:00:00 2001 From: khris-xp Date: Thu, 14 Nov 2024 16:17:20 +0700 Subject: [PATCH 029/155] feat: chapter module service --- src/app.controller.spec.ts | 24 +- src/app.controller.ts | 12 +- src/app.module.ts | 2 + src/app.service.ts | 6 +- src/auth/auth.controller.ts | 55 +-- src/auth/auth.guard.ts | 80 ++-- src/auth/auth.module.ts | 21 +- src/auth/auth.service.ts | 168 ++++---- src/auth/dtos/auth-response.dto.ts | 36 +- src/auth/dtos/jwt-payload.dto.ts | 8 +- src/auth/dtos/login.dto.ts | 34 +- src/auth/dtos/register.dto.ts | 4 +- .../authenticated-request.interface.ts | 4 +- src/category/category.entity.ts | 1 - src/chapter/chapter.controller.ts | 132 ++++++ src/chapter/chapter.entity.ts | 87 ++++ src/chapter/chapter.module.ts | 14 + src/chapter/chapter.provider.ts | 10 + src/chapter/chapter.service.ts | 162 ++++++++ src/chapter/dtos/chapter-response.dto.ts | 110 +++++ src/chapter/dtos/create-chapter.dto.ts | 85 ++++ src/chapter/dtos/update-chapter.dto.ts | 4 + src/course-module/course-module.entity.ts | 5 + src/course/course.module.ts | 29 +- src/course/course.provider.ts | 11 +- src/course/course.service.ts | 388 +++++++++--------- src/course/dtos/create-course.dto.ts | 135 +++--- src/course/dtos/index.ts | 2 +- src/course/dtos/update-course.dto.ts | 5 +- src/database/database.module.ts | 10 +- src/database/database.providers.ts | 16 +- src/shared/configs/database.config.ts | 13 +- src/shared/configs/dotenv.config.ts | 38 +- .../constants/global-config.constant.ts | 28 +- src/shared/decorators/public.decorator.ts | 6 +- src/shared/decorators/role.decorator.ts | 2 +- src/shared/enums/course-level.enum.ts | 6 +- src/shared/enums/course-status.enum.ts | 6 +- src/shared/enums/environment.enum.ts | 6 +- src/shared/enums/roles.enum.ts | 12 +- src/shared/guards/role.guard.ts | 21 +- .../dtos/user-streak-response.dto.ts | 122 +++--- src/user-streak/user-streak.controller.ts | 100 ++--- src/user-streak/user-streak.entity.ts | 74 ++-- src/user-streak/user-streak.module.ts | 25 +- src/user-streak/user-streak.providers.ts | 13 +- src/user-streak/user-streak.service.ts | 116 +++--- src/user/dtos/update-user.dto.ts | 40 +- src/user/user.entity.ts | 111 ++--- src/user/user.providers.ts | 10 +- src/user/user.service.ts | 104 ++--- 51 files changed, 1590 insertions(+), 923 deletions(-) create mode 100644 src/chapter/chapter.controller.ts create mode 100644 src/chapter/chapter.entity.ts create mode 100644 src/chapter/chapter.module.ts create mode 100644 src/chapter/chapter.provider.ts create mode 100644 src/chapter/chapter.service.ts create mode 100644 src/chapter/dtos/chapter-response.dto.ts create mode 100644 src/chapter/dtos/create-chapter.dto.ts create mode 100644 src/chapter/dtos/update-chapter.dto.ts diff --git a/src/app.controller.spec.ts b/src/app.controller.spec.ts index 2552ec5..d22f389 100644 --- a/src/app.controller.spec.ts +++ b/src/app.controller.spec.ts @@ -3,20 +3,20 @@ import { AppController } from './app.controller'; import { AppService } from './app.service'; describe('AppController', () => { - let appController: AppController; + let appController: AppController; - beforeEach(async () => { - const app: TestingModule = await Test.createTestingModule({ - controllers: [AppController], - providers: [AppService], - }).compile(); + beforeEach(async () => { + const app: TestingModule = await Test.createTestingModule({ + controllers: [AppController], + providers: [AppService], + }).compile(); - appController = app.get(AppController); - }); + appController = app.get(AppController); + }); - describe('root', () => { - it('should return "Hello World!"', () => { - expect(appController.getHello()).toBe('Hello World!'); - }); + describe('root', () => { + it('should return "Hello World!"', () => { + expect(appController.getHello()).toBe('Hello World!'); }); + }); }); diff --git a/src/app.controller.ts b/src/app.controller.ts index d27ee79..2cab0eb 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -4,11 +4,11 @@ import { Public } from './shared/decorators/public.decorator'; @Controller() export class AppController { - constructor(private readonly appService: AppService) { } + constructor(private readonly appService: AppService) {} - @Get() - @Public() - getHello(): string { - return this.appService.getHello(); - } + @Get() + @Public() + getHello(): string { + return this.appService.getHello(); + } } diff --git a/src/app.module.ts b/src/app.module.ts index 64a4c20..e9fd6e7 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -8,6 +8,7 @@ import { AppService } from './app.service'; import { AuthGuard } from './auth/auth.guard'; import { AuthModule } from './auth/auth.module'; import { CategoryModule } from './category/category.module'; +import { ChapterModule } from './chapter/chapter.module'; import { CourseModuleModule } from './course-module/course-module.module'; import { Course } from './course/course.entity'; import { CourseModule } from './course/course.module'; @@ -50,6 +51,7 @@ const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); CategoryModule, CourseModule, CourseModuleModule, + ChapterModule, ], controllers: [AppController], providers: [ diff --git a/src/app.service.ts b/src/app.service.ts index 61b7a5b..927d7cc 100644 --- a/src/app.service.ts +++ b/src/app.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; @Injectable() export class AppService { - getHello(): string { - return 'Hello World!'; - } + getHello(): string { + return 'Hello World!'; + } } diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 367c9a0..8cc37d9 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -1,4 +1,11 @@ -import { Controller, Injectable, Post, Body, HttpCode, HttpStatus } from '@nestjs/common'; +import { + Controller, + Injectable, + Post, + Body, + HttpCode, + HttpStatus, +} from '@nestjs/common'; import { AuthService } from './auth.service'; import { LoginDto } from './dtos/login.dto'; import { RegisterDto } from './dtos/register.dto'; @@ -10,30 +17,28 @@ import { AuthResponseDto } from './dtos/auth-response.dto'; @ApiTags('Auth') @Injectable() export class AuthController { - constructor( - private readonly authService: AuthService, - ) { } + constructor(private readonly authService: AuthService) {} - @Post('login') - @ApiResponse({ - status: HttpStatus.OK, - description: 'Login', - type: AuthResponseDto - }) - @Public() - async login(@Body() loginDto: LoginDto): Promise { - return await this.authService.login(loginDto); - } + @Post('login') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Login', + type: AuthResponseDto, + }) + @Public() + async login(@Body() loginDto: LoginDto): Promise { + return await this.authService.login(loginDto); + } - @Post('register') - @ApiResponse({ - status: HttpStatus.CREATED, - description: 'Register', - type: AuthResponseDto - }) - @HttpCode(HttpStatus.CREATED) - @Public() - async register(@Body() registerDto: RegisterDto): Promise { - return await this.authService.register(registerDto); - } + @Post('register') + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Register', + type: AuthResponseDto, + }) + @HttpCode(HttpStatus.CREATED) + @Public() + async register(@Body() registerDto: RegisterDto): Promise { + return await this.authService.register(registerDto); + } } diff --git a/src/auth/auth.guard.ts b/src/auth/auth.guard.ts index ccf63e0..e96e76e 100644 --- a/src/auth/auth.guard.ts +++ b/src/auth/auth.guard.ts @@ -1,50 +1,46 @@ import { - Injectable, - CanActivate, - ExecutionContext, - UnauthorizedException, -} from "@nestjs/common"; -import { JwtService } from "@nestjs/jwt"; -import { ConfigService } from "@nestjs/config"; -import { GLOBAL_CONFIG } from "src/shared/constants/global-config.constant"; -import { JwtPayloadDto } from "./dtos/jwt-payload.dto"; -import { IS_PUBLIC_KEY } from "src/shared/decorators/public.decorator"; -import { Reflector } from "@nestjs/core"; -import { Request } from "express"; + Injectable, + CanActivate, + ExecutionContext, + UnauthorizedException, +} from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { ConfigService } from '@nestjs/config'; +import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; +import { JwtPayloadDto } from './dtos/jwt-payload.dto'; +import { IS_PUBLIC_KEY } from 'src/shared/decorators/public.decorator'; +import { Reflector } from '@nestjs/core'; +import { Request } from 'express'; @Injectable() export class AuthGuard implements CanActivate { - constructor( - private readonly jwtService: JwtService, - private readonly configService: ConfigService, - private readonly reflector: Reflector - ) {} + constructor( + private readonly jwtService: JwtService, + private readonly configService: ConfigService, + private readonly reflector: Reflector, + ) {} - async canActivate(context: ExecutionContext): Promise { - const isPublic = this.reflector.getAllAndOverride( - IS_PUBLIC_KEY, - [context.getHandler(), context.getClass()] - ); - if (isPublic) return true; - const request = context.switchToHttp().getRequest(); - const token = this.extractTokenFromHeader(request); - if (!token) - throw new UnauthorizedException("Unauthorized access"); - try { - request.user = - await this.jwtService.verifyAsync(token, { - secret: this.configService.get( - GLOBAL_CONFIG.JWT_ACCESS_SECRET - ), - }); - } catch (error) { - throw new UnauthorizedException("Unauthorized access"); - } - return true; + async canActivate(context: ExecutionContext): Promise { + const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ + context.getHandler(), + context.getClass(), + ]); + if (isPublic) return true; + const request = context.switchToHttp().getRequest(); + const token = this.extractTokenFromHeader(request); + if (!token) throw new UnauthorizedException('Unauthorized access'); + try { + request.user = await this.jwtService.verifyAsync(token, { + secret: this.configService.get(GLOBAL_CONFIG.JWT_ACCESS_SECRET), + }); + } catch (error) { + throw new UnauthorizedException('Unauthorized access'); } + return true; + } - private extractTokenFromHeader(request: Request): string | undefined { - const [type, token] = request.headers.authorization?.split(" ") ?? []; - return type === "Bearer" ? token : undefined; - } + private extractTokenFromHeader(request: Request): string | undefined { + const [type, token] = request.headers.authorization?.split(' ') ?? []; + return type === 'Bearer' ? token : undefined; + } } diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index f789649..42cc0f1 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -1,15 +1,12 @@ -import { Module } from "@nestjs/common"; -import { AuthController } from "./auth.controller"; -import { AuthService } from "./auth.service"; -import { UserModule } from "src/user/user.module"; -import { UserStreakModule } from "src/user-streak/user-streak.module"; +import { Module } from '@nestjs/common'; +import { AuthController } from './auth.controller'; +import { AuthService } from './auth.service'; +import { UserModule } from 'src/user/user.module'; +import { UserStreakModule } from 'src/user-streak/user-streak.module'; @Module({ - imports: [ - UserModule, - UserStreakModule, - ], - controllers: [AuthController], - providers: [AuthService], + imports: [UserModule, UserStreakModule], + controllers: [AuthController], + providers: [AuthService], }) -export class AuthModule { } \ No newline at end of file +export class AuthModule {} diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index ddec553..ebd4ca6 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -1,81 +1,105 @@ -import { Injectable, NotFoundException, BadRequestException, InternalServerErrorException } from "@nestjs/common"; -import { ConfigService } from "@nestjs/config"; -import { JwtService } from "@nestjs/jwt"; -import { LoginDto } from "./dtos/login.dto"; -import { RegisterDto } from "./dtos/register.dto"; -import { UserService } from "src/user/user.service"; -import { hash, verify } from "argon2"; -import { AuthResponseDto } from "./dtos/auth-response.dto"; -import { JwtPayloadDto } from "./dtos/jwt-payload.dto"; -import { GLOBAL_CONFIG } from "src/shared/constants/global-config.constant"; -import { UserResponseDto } from "src/user/dtos/user-response.dto"; -import { UserStreakService } from "src/user-streak/user-streak.service"; +import { + Injectable, + NotFoundException, + BadRequestException, + InternalServerErrorException, +} from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { JwtService } from '@nestjs/jwt'; +import { LoginDto } from './dtos/login.dto'; +import { RegisterDto } from './dtos/register.dto'; +import { UserService } from 'src/user/user.service'; +import { hash, verify } from 'argon2'; +import { AuthResponseDto } from './dtos/auth-response.dto'; +import { JwtPayloadDto } from './dtos/jwt-payload.dto'; +import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; +import { UserResponseDto } from 'src/user/dtos/user-response.dto'; +import { UserStreakService } from 'src/user-streak/user-streak.service'; @Injectable() export class AuthService { - constructor( - private readonly configService: ConfigService, - private readonly jwtService: JwtService, - private readonly userService: UserService, - private readonly userStreakService: UserStreakService, - ) { } + constructor( + private readonly configService: ConfigService, + private readonly jwtService: JwtService, + private readonly userService: UserService, + private readonly userStreakService: UserStreakService, + ) {} - async login(loginDto: LoginDto): Promise { - const user = await this.userService.findOne({ where: { email: loginDto.email } }); - if (!user) - throw new NotFoundException("User not found"); - const isPasswordValid = await verify(user.password, loginDto.password); - if (!isPasswordValid) - throw new BadRequestException("Invalid password"); - try { - const accessToken = this.generateAccessToken({ id: user.id, role: user.role }); - const refreshToken = this.generateRefreshToken(); - await this.userStreakService.update(user.id); - return { - accessToken, - refreshToken, - user: new UserResponseDto(user), - } - } catch (error) { - if (error instanceof Error) - throw new InternalServerErrorException(error.message); - } + async login(loginDto: LoginDto): Promise { + const user = await this.userService.findOne({ + where: { email: loginDto.email }, + }); + if (!user) throw new NotFoundException('User not found'); + const isPasswordValid = await verify(user.password, loginDto.password); + if (!isPasswordValid) throw new BadRequestException('Invalid password'); + try { + const accessToken = this.generateAccessToken({ + id: user.id, + role: user.role, + }); + const refreshToken = this.generateRefreshToken(); + await this.userStreakService.update(user.id); + return { + accessToken, + refreshToken, + user: new UserResponseDto(user), + }; + } catch (error) { + if (error instanceof Error) + throw new InternalServerErrorException(error.message); } + } - async register(registerDto: RegisterDto): Promise { - const user = await this.userService.findOne({ where: { email: registerDto.email } }); - if (user) - throw new BadRequestException("User already exists"); - const hashedPassword = await hash(registerDto.password); - const createdUser = await this.userService.create({ ...registerDto, password: hashedPassword }); - try { - const accessToken = this.generateAccessToken({ id: createdUser.id, role: createdUser.role }); - const refreshToken = this.generateRefreshToken(); - await this.userStreakService.create(createdUser.id); - return { - accessToken, - refreshToken, - user: new UserResponseDto(createdUser), - } - } catch (error) { - if (error instanceof Error) { - await this.userService.delete(createdUser.id); - throw new InternalServerErrorException(error.message); - } - } + async register(registerDto: RegisterDto): Promise { + const user = await this.userService.findOne({ + where: { email: registerDto.email }, + }); + if (user) throw new BadRequestException('User already exists'); + const hashedPassword = await hash(registerDto.password); + const createdUser = await this.userService.create({ + ...registerDto, + password: hashedPassword, + }); + try { + const accessToken = this.generateAccessToken({ + id: createdUser.id, + role: createdUser.role, + }); + const refreshToken = this.generateRefreshToken(); + await this.userStreakService.create(createdUser.id); + return { + accessToken, + refreshToken, + user: new UserResponseDto(createdUser), + }; + } catch (error) { + if (error instanceof Error) { + await this.userService.delete(createdUser.id); + throw new InternalServerErrorException(error.message); + } } + } - generateAccessToken(payload: JwtPayloadDto): string { - return this.jwtService.sign(payload, { - secret: this.configService.get(GLOBAL_CONFIG.JWT_ACCESS_SECRET), - expiresIn: this.configService.get(GLOBAL_CONFIG.JWT_ACCESS_EXPIRATION), - }); - } + generateAccessToken(payload: JwtPayloadDto): string { + return this.jwtService.sign(payload, { + secret: this.configService.get(GLOBAL_CONFIG.JWT_ACCESS_SECRET), + expiresIn: this.configService.get( + GLOBAL_CONFIG.JWT_ACCESS_EXPIRATION, + ), + }); + } - generateRefreshToken(): string { - return this.jwtService.sign({}, { - secret: this.configService.get(GLOBAL_CONFIG.JWT_REFRESH_SECRET), - expiresIn: this.configService.get(GLOBAL_CONFIG.JWT_REFRESH_EXPIRATION), - }); - } -} \ No newline at end of file + generateRefreshToken(): string { + return this.jwtService.sign( + {}, + { + secret: this.configService.get( + GLOBAL_CONFIG.JWT_REFRESH_SECRET, + ), + expiresIn: this.configService.get( + GLOBAL_CONFIG.JWT_REFRESH_EXPIRATION, + ), + }, + ); + } +} diff --git a/src/auth/dtos/auth-response.dto.ts b/src/auth/dtos/auth-response.dto.ts index 6fc3440..4921c8a 100644 --- a/src/auth/dtos/auth-response.dto.ts +++ b/src/auth/dtos/auth-response.dto.ts @@ -1,22 +1,22 @@ -import { UserResponseDto } from "src/user/dtos/user-response.dto"; -import { ApiProperty } from "@nestjs/swagger"; +import { UserResponseDto } from 'src/user/dtos/user-response.dto'; +import { ApiProperty } from '@nestjs/swagger'; export class AuthResponseDto { - @ApiProperty({ - description: 'Access token', - type: String, - }) - accessToken: string; + @ApiProperty({ + description: 'Access token', + type: String, + }) + accessToken: string; - @ApiProperty({ - description: 'Refresh token', - type: String, - }) - refreshToken: string; + @ApiProperty({ + description: 'Refresh token', + type: String, + }) + refreshToken: string; - @ApiProperty({ - description: 'User', - type: UserResponseDto, - }) - user: UserResponseDto; -} \ No newline at end of file + @ApiProperty({ + description: 'User', + type: UserResponseDto, + }) + user: UserResponseDto; +} diff --git a/src/auth/dtos/jwt-payload.dto.ts b/src/auth/dtos/jwt-payload.dto.ts index 1544564..b9744b1 100644 --- a/src/auth/dtos/jwt-payload.dto.ts +++ b/src/auth/dtos/jwt-payload.dto.ts @@ -1,6 +1,6 @@ -import { Role } from "src/shared/enums/roles.enum"; +import { Role } from 'src/shared/enums/roles.enum'; export class JwtPayloadDto { - id: string; - role: Role; -} \ No newline at end of file + id: string; + role: Role; +} diff --git a/src/auth/dtos/login.dto.ts b/src/auth/dtos/login.dto.ts index 97fbfd3..dab189d 100644 --- a/src/auth/dtos/login.dto.ts +++ b/src/auth/dtos/login.dto.ts @@ -2,21 +2,21 @@ import { IsString, IsEmail, IsNotEmpty } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; export class LoginDto { - @IsEmail() - @IsNotEmpty() - @ApiProperty({ - description: 'User email', - type: String, - example: 'johndoe@gmail.com', - }) - email: string; + @IsEmail() + @IsNotEmpty() + @ApiProperty({ + description: 'User email', + type: String, + example: 'johndoe@gmail.com', + }) + email: string; - @IsString() - @IsNotEmpty() - @ApiProperty({ - description: 'User password', - type: String, - example: 'P@ssw0rd!', - }) - password: string; -} \ No newline at end of file + @IsString() + @IsNotEmpty() + @ApiProperty({ + description: 'User password', + type: String, + example: 'P@ssw0rd!', + }) + password: string; +} diff --git a/src/auth/dtos/register.dto.ts b/src/auth/dtos/register.dto.ts index 1f20e7a..846da5f 100644 --- a/src/auth/dtos/register.dto.ts +++ b/src/auth/dtos/register.dto.ts @@ -1,3 +1,3 @@ -import { CreateUserDto } from "src/user/dtos/create-user.dto"; +import { CreateUserDto } from 'src/user/dtos/create-user.dto'; -export class RegisterDto extends CreateUserDto { } \ No newline at end of file +export class RegisterDto extends CreateUserDto {} diff --git a/src/auth/interfaces/authenticated-request.interface.ts b/src/auth/interfaces/authenticated-request.interface.ts index 7a2d028..a0ec097 100644 --- a/src/auth/interfaces/authenticated-request.interface.ts +++ b/src/auth/interfaces/authenticated-request.interface.ts @@ -1,5 +1,5 @@ -import { JwtPayloadDto } from "../dtos/jwt-payload.dto"; +import { JwtPayloadDto } from '../dtos/jwt-payload.dto'; export interface AuthenticatedRequest extends Request { - user: JwtPayloadDto; + user: JwtPayloadDto; } diff --git a/src/category/category.entity.ts b/src/category/category.entity.ts index f2ea1e1..5c4f186 100644 --- a/src/category/category.entity.ts +++ b/src/category/category.entity.ts @@ -32,7 +32,6 @@ export class Category { @OneToMany(() => Course, (course) => course.category) courses: Course[]; - @CreateDateColumn({ type: 'timestamp', nullable: false, diff --git a/src/chapter/chapter.controller.ts b/src/chapter/chapter.controller.ts new file mode 100644 index 0000000..4f6d0ae --- /dev/null +++ b/src/chapter/chapter.controller.ts @@ -0,0 +1,132 @@ +import { + Body, + Controller, + Delete, + Get, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, +} from '@nestjs/common'; +import { + ApiBearerAuth, + ApiParam, + ApiQuery, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { ChapterService } from './chapter.service'; +import { + ChapterResponseDto, + PaginatedChapterResponseDto, +} from './dtos/chapter-response.dto'; +import { CreateChapterDto } from './dtos/create-chapter.dto'; +import { UpdateChapterDto } from './dtos/update-chapter.dto'; + +@Controller('chapter') +@ApiTags('Chapters') +@ApiBearerAuth() +@Injectable() +export class ChapterController { + constructor(private readonly chapterService: ChapterService) {} + + @Get() + @ApiResponse({ + status: HttpStatus.OK, + type: ChapterResponseDto, + description: 'Get all chapters', + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + async findAll( + @Query() query: PaginateQueryDto, + ): Promise { + return this.chapterService.findAll(query); + } + + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + type: ChapterResponseDto, + description: 'Get a chapter by ID', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'Chapter ID', + }) + async findOne( + @Param('id', ParseUUIDPipe) id: string, + ): Promise { + return this.chapterService.findOne(id, { where: { id } }); + } + + @Post() + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.CREATED, + type: ChapterResponseDto, + description: 'Create a chapter', + }) + async create( + @Body() createChapterDto: CreateChapterDto, + ): Promise { + return this.chapterService.create(createChapterDto); + } + + @Patch(':id') + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.OK, + type: ChapterResponseDto, + description: 'Update a chapter', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'Chapter ID', + }) + async update( + @Param('id', ParseUUIDPipe) id: string, + @Body() updateChapterDto: UpdateChapterDto, + ): Promise { + return this.chapterService.update(id, updateChapterDto); + } + + @Delete(':id') + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + type: ChapterResponseDto, + description: 'Delete a chapter', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'Chapter ID', + }) + async remove( + @Param('id', ParseUUIDPipe) id: string, + ): Promise { + return this.chapterService.remove(id); + } +} diff --git a/src/chapter/chapter.entity.ts b/src/chapter/chapter.entity.ts new file mode 100644 index 0000000..6d6e5d4 --- /dev/null +++ b/src/chapter/chapter.entity.ts @@ -0,0 +1,87 @@ +import { CourseModule } from 'src/course-module/course-module.entity'; +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +@Entity() +export class Chapter { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ + type: String, + nullable: false, + }) + title: string; + + @Column({ + type: String, + nullable: false, + }) + description: string; + + @ManyToOne(() => CourseModule, (module) => module.chapters, { + onDelete: 'CASCADE', + nullable: false, + }) + @JoinColumn({ name: 'module_id' }) + module: CourseModule; + + @Column({ name: 'module_id' }) + moduleId: string; + + @Column({ + type: String, + nullable: false, + }) + videoUrl: string; + + @Column({ + type: String, + nullable: false, + }) + content: string; + + @Column({ + type: String, + nullable: false, + }) + summary: string; + + @Column({ + type: Number, + nullable: false, + }) + duration: number; + + @Column({ + type: Number, + nullable: false, + }) + orderIndex: number; + + @Column({ + type: Boolean, + nullable: false, + default: true, + }) + isPreview: boolean; + + @CreateDateColumn({ + type: 'timestamp', + name: 'created_at', + }) + createdAt: Date; + + @UpdateDateColumn({ + type: 'timestamp', + name: 'updated_at', + }) + updatedAt: Date; +} diff --git a/src/chapter/chapter.module.ts b/src/chapter/chapter.module.ts new file mode 100644 index 0000000..bfdb840 --- /dev/null +++ b/src/chapter/chapter.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { DatabaseModule } from 'src/database/database.module'; +import { ChapterController } from './chapter.controller'; +import { Chapter } from './chapter.entity'; +import { chapterProviders } from './chapter.provider'; +import { ChapterService } from './chapter.service'; + +@Module({ + imports: [DatabaseModule], + controllers: [ChapterController], + providers: [...chapterProviders, ChapterService], + exports: [ChapterService], +}) +export class ChapterModule {} diff --git a/src/chapter/chapter.provider.ts b/src/chapter/chapter.provider.ts new file mode 100644 index 0000000..c85b713 --- /dev/null +++ b/src/chapter/chapter.provider.ts @@ -0,0 +1,10 @@ +import { DataSource } from 'typeorm'; +import { Chapter } from './chapter.entity'; + +export const chapterProviders = [ + { + provide: 'ChapterRepository', + useFactory: (dataSource: DataSource) => dataSource.getRepository(Chapter), + inject: ['DataSource'], + }, +]; diff --git a/src/chapter/chapter.service.ts b/src/chapter/chapter.service.ts new file mode 100644 index 0000000..9e06bd4 --- /dev/null +++ b/src/chapter/chapter.service.ts @@ -0,0 +1,162 @@ +import { + BadRequestException, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { createPagination } from 'src/shared/pagination'; +import { FindOneOptions, FindOptionsWhere, ILike, Repository } from 'typeorm'; +import { Chapter } from './chapter.entity'; +import { PaginatedChapterResponseDto } from './dtos/chapter-response.dto'; +import { CreateChapterDto } from './dtos/create-chapter.dto'; +import { UpdateChapterDto } from './dtos/update-chapter.dto'; + +@Injectable() +export class ChapterService { + constructor( + @InjectRepository(Chapter) + private readonly chapterRepository: Repository, + ) {} + + async findAll({ + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }): Promise { + const { find } = await createPagination(this.chapterRepository, { + page, + limit, + }); + + const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; + const whereCondition = { ...baseSearch }; + + const chapters = await find({ + where: whereCondition, + relations: { + module: true, + }, + }).run(); + + return chapters; + } + + async findOne( + id: string, + options: FindOneOptions, + ): Promise { + const baseWhere = options.where as FindOptionsWhere; + const whereCondition = { ...baseWhere, id }; + + const chapter = await this.chapterRepository.findOne({ + where: whereCondition, + relations: { + module: true, + }, + }); + + if (!chapter) { + throw new NotFoundException('Chapter not found'); + } + + return chapter; + } + + async validateAndGetNextOrderIndex(moduleId: string): Promise { + const existingChapter = await this.chapterRepository.find({ + where: { moduleId }, + order: { orderIndex: 'DESC' }, + }); + + const nextOrderIndex = existingChapter.map((chapter) => chapter.orderIndex); + const hasDuplicates = + new Set(nextOrderIndex).size !== nextOrderIndex.length; + + if (hasDuplicates) { + throw new BadRequestException('Order index is duplicated'); + } + + return nextOrderIndex.length ? nextOrderIndex[0] + 1 : 1; + } + + async create(createChapterDto: CreateChapterDto): Promise { + if (!createChapterDto.orderIndex) { + createChapterDto.orderIndex = await this.validateAndGetNextOrderIndex( + createChapterDto.moduleId, + ); + } else { + const existingChapter = await this.chapterRepository.findOne({ + where: { orderIndex: createChapterDto.orderIndex }, + }); + + if (existingChapter) { + throw new BadRequestException('Order index is duplicated'); + } + } + + const chapter = this.chapterRepository.create(createChapterDto); + + await this.chapterRepository.save(chapter); + return chapter; + } + + async reorderModules(moduleId: string): Promise { + const modulesToReorder = await this.chapterRepository.find({ + where: { moduleId }, + order: { orderIndex: 'ASC' }, + }); + + for (let i = 0; i < modulesToReorder.length; i++) { + modulesToReorder[i].orderIndex = i + 1; + } + + await this.chapterRepository.save(modulesToReorder); + } + + async update( + id: string, + updateChapterDto: UpdateChapterDto, + ): Promise { + const chapter = await this.findOne(id, { where: { id } }); + + if (!chapter) { + throw new NotFoundException('Chapter not found'); + } + + if ( + updateChapterDto.orderIndex && + updateChapterDto.orderIndex !== chapter.orderIndex + ) { + const existingChapter = await this.chapterRepository.findOne({ + where: { orderIndex: updateChapterDto.orderIndex }, + }); + + if (existingChapter) { + throw new BadRequestException('Order index is duplicated'); + } + } + + this.chapterRepository.merge(chapter, updateChapterDto); + await this.chapterRepository.save(chapter); + + return chapter; + } + + async remove(id: string): Promise { + const chapter = await this.findOne(id, { where: { id } }); + + if (!chapter) { + throw new BadRequestException('Chapter not found'); + } + + const result = await this.chapterRepository.remove(chapter); + + await this.reorderModules(chapter.moduleId); + + return result; + } +} diff --git a/src/chapter/dtos/chapter-response.dto.ts b/src/chapter/dtos/chapter-response.dto.ts new file mode 100644 index 0000000..4ffe5fc --- /dev/null +++ b/src/chapter/dtos/chapter-response.dto.ts @@ -0,0 +1,110 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { CourseModuleResponseDto } from 'src/course-module/dtos/course-module-response.dto'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { Chapter } from '../chapter.entity'; + +export class ChapterResponseDto { + @ApiProperty({ + description: 'Chapter ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + id: string; + + @ApiProperty({ + description: 'Chapter title', + type: String, + example: 'Introduction to Variables', + }) + title: string; + + @ApiProperty({ + description: 'Chapter description', + type: String, + example: 'Learn about variables and data types in programming', + }) + description: string; + + @ApiProperty({ + description: 'Chapter video URL', + type: String, + example: 'https://www.youtube.com/watch?v=8k-9mU5KfBQ', + }) + videoUrl: string; + + @ApiProperty({ + description: 'Chapter content', + type: String, + example: 'This chapter covers the basics of programming', + }) + content: string; + + @ApiProperty({ + description: 'Chapter summary', + type: String, + example: 'This chapter is an introduction to programming', + }) + summary: string; + + @ApiProperty({ + description: 'Chapter duration', + type: Number, + example: 10, + }) + duration: number; + + @ApiProperty({ + description: 'Chapter module', + type: CourseModuleResponseDto, + example: { + id: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + title: 'Introduction to Programming', + description: 'This module is an introduction to programming', + orderIndex: 1, + courseId: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }, + }) + module: CourseModuleResponseDto; + + @ApiProperty({ + description: 'Chapter created date', + type: Date, + example: new Date(), + }) + createdAt: Date; + + @ApiProperty({ + description: 'Chapter updated date', + type: Date, + example: new Date(), + }) + updatedAt: Date; + + constructor(chapter: Chapter) { + this.id = chapter.id; + this.title = chapter.title; + this.description = chapter.description; + this.videoUrl = chapter.videoUrl; + this.content = chapter.content; + this.summary = chapter.summary; + this.duration = chapter.duration; + this.createdAt = chapter.createdAt; + this.updatedAt = chapter.updatedAt; + } +} + +export class PaginatedChapterResponseDto extends PaginatedResponse( + ChapterResponseDto, +) { + constructor( + chapters: Chapter[], + total: number, + pageSize: number, + currentPage: number, + ) { + const chapterDtos = chapters.map( + (chapter) => new ChapterResponseDto(chapter), + ); + super(chapterDtos, total, pageSize, currentPage); + } +} diff --git a/src/chapter/dtos/create-chapter.dto.ts b/src/chapter/dtos/create-chapter.dto.ts new file mode 100644 index 0000000..2a89f06 --- /dev/null +++ b/src/chapter/dtos/create-chapter.dto.ts @@ -0,0 +1,85 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsNotEmpty, IsNumber, IsString } from 'class-validator'; + +export class CreateChapterDto { + @IsNotEmpty() + @IsString() + @ApiProperty({ + description: 'Chapter title', + type: String, + example: 'Introduction to Programming', + }) + title: string; + + @IsNotEmpty() + @IsString() + @ApiProperty({ + description: 'Chapter description', + type: String, + example: 'This chapter is an introduction to programming', + }) + description: string; + + @IsNotEmpty() + @IsString() + @ApiProperty({ + description: 'Chapter video URL', + type: String, + example: 'https://www.youtube.com/watch?v=8k-9mU5KfBQ', + }) + videoUrl: string; + + @IsNotEmpty() + @IsString() + @ApiProperty({ + description: 'Chapter content', + type: String, + example: 'This chapter covers the basics of programming', + }) + content: string; + + @IsNotEmpty() + @IsString() + @ApiProperty({ + description: 'Chapter summary', + type: String, + example: 'This chapter is an introduction to programming', + }) + summary: string; + + @IsNotEmpty() + @IsNumber() + @ApiProperty({ + description: 'Chapter duration', + type: Number, + example: 10, + }) + duration: number; + + @IsNotEmpty() + @IsNumber() + @ApiProperty({ + description: 'Chapter order index', + type: Number, + example: 1, + }) + orderIndex: number; + + @IsNotEmpty() + @IsString() + @ApiProperty({ + description: 'Module ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + moduleId: string; + + @IsNotEmpty() + @IsBoolean() + @ApiProperty({ + description: 'Chapter preview', + type: Boolean, + example: true, + }) + isPreview: boolean; +} diff --git a/src/chapter/dtos/update-chapter.dto.ts b/src/chapter/dtos/update-chapter.dto.ts new file mode 100644 index 0000000..7a4eaa6 --- /dev/null +++ b/src/chapter/dtos/update-chapter.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateChapterDto } from './create-chapter.dto'; + +export class UpdateChapterDto extends PartialType(CreateChapterDto) {} diff --git a/src/course-module/course-module.entity.ts b/src/course-module/course-module.entity.ts index 7f52588..ae9b273 100644 --- a/src/course-module/course-module.entity.ts +++ b/src/course-module/course-module.entity.ts @@ -1,3 +1,4 @@ +import { Chapter } from 'src/chapter/chapter.entity'; import { Course } from 'src/course/course.entity'; import { Column, @@ -5,6 +6,7 @@ import { Entity, JoinColumn, ManyToOne, + OneToMany, PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; @@ -32,6 +34,9 @@ export class CourseModule { }) orderIndex: number; + @OneToMany(() => Chapter, (chapter) => chapter.module) + chapters: Chapter[]; + @ManyToOne(() => Course, (course) => course.modules, { onDelete: 'CASCADE', nullable: false, diff --git a/src/course/course.module.ts b/src/course/course.module.ts index f295c3c..55a16b1 100644 --- a/src/course/course.module.ts +++ b/src/course/course.module.ts @@ -1,21 +1,14 @@ -import { Module } from "@nestjs/common"; -import { CourseService } from "./course.service"; -import { CourseController } from "./course.controller"; -import { courseProviders } from "./course.provider"; -import { DatabaseModule } from "src/database/database.module"; -import { CategoryModule } from "src/category/category.module"; - +import { Module } from '@nestjs/common'; +import { CategoryModule } from 'src/category/category.module'; +import { DatabaseModule } from 'src/database/database.module'; +import { CourseController } from './course.controller'; +import { courseProviders } from './course.provider'; +import { CourseService } from './course.service'; @Module({ - imports: [ - DatabaseModule, - CategoryModule - ], - controllers: [CourseController], - providers: [ - ...courseProviders, - CourseService, - ], - exports: [CourseService], + imports: [DatabaseModule, CategoryModule], + controllers: [CourseController], + providers: [...courseProviders, CourseService], + exports: [CourseService], }) -export class CourseModule { } \ No newline at end of file +export class CourseModule {} diff --git a/src/course/course.provider.ts b/src/course/course.provider.ts index 30a2cde..492e8a3 100644 --- a/src/course/course.provider.ts +++ b/src/course/course.provider.ts @@ -1,9 +1,10 @@ -import { DataSource } from "typeorm"; -import { Course } from "./course.entity"; +import { DataSource } from 'typeorm'; +import { Course } from './course.entity'; - -export const courseProviders = [{ +export const courseProviders = [ + { provide: 'CourseRepository', useFactory: (dataSource: DataSource) => dataSource.getRepository(Course), inject: ['DataSource'], -}]; \ No newline at end of file + }, +]; diff --git a/src/course/course.service.ts b/src/course/course.service.ts index b9a91b9..b132a36 100644 --- a/src/course/course.service.ts +++ b/src/course/course.service.ts @@ -1,200 +1,222 @@ -import { BadRequestException, ForbiddenException, Inject, Injectable, NotFoundException } from "@nestjs/common"; -import { FindOneOptions, FindOptionsWhere, ILike, Repository } from "typeorm"; -import { Course } from "./course.entity"; -import { UpdateCourseDto, CreateCourseDto, PaginatedCourseResponeDto } from "./dtos/index"; -import { CourseStatus, Role } from "src/shared/enums"; -import { createPagination } from "src/shared/pagination"; - - +import { + BadRequestException, + ForbiddenException, + Inject, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { CourseStatus, Role } from 'src/shared/enums'; +import { createPagination } from 'src/shared/pagination'; +import { FindOneOptions, FindOptionsWhere, ILike, Repository } from 'typeorm'; +import { Course } from './course.entity'; +import { + CreateCourseDto, + PaginatedCourseResponeDto, + UpdateCourseDto, +} from './dtos/index'; @Injectable() export class CourseService { - constructor( - @Inject("CourseRepository") - private readonly courseRepository: Repository, - ) { } - - async findAll({ - page = 1, - limit = 20, - search = '', - userId, - role - }: { - page?: number; - limit?: number; - search?: string; - userId: string; - role: Role; - }): Promise { - const { find } = await createPagination(this.courseRepository, { - page, - limit, - }); - - const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; - const whereCondition = this.buildWhereCondition(userId, role, baseSearch); - - const courses = await find({ - where: whereCondition, - relations: { - teacher: true, - category: true - } - }).run(); - - return courses; + constructor( + @Inject('CourseRepository') + private readonly courseRepository: Repository, + ) {} + + async findAll({ + page = 1, + limit = 20, + search = '', + userId, + role, + }: { + page?: number; + limit?: number; + search?: string; + userId: string; + role: Role; + }): Promise { + const { find } = await createPagination(this.courseRepository, { + page, + limit, + }); + + const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; + const whereCondition = this.buildWhereCondition(userId, role, baseSearch); + + const courses = await find({ + where: whereCondition, + relations: { + teacher: true, + category: true, + }, + }).run(); + + return courses; + } + + async findOne( + userId: string, + role: Role, + options: FindOneOptions, + ): Promise { + const baseWhere = options.where as FindOptionsWhere; + const whereCondition = this.buildWhereCondition(userId, role, baseWhere); + + const course = await this.courseRepository.findOne({ + where: whereCondition, + relations: { + teacher: true, + category: true, + }, + }); + + if (!course) { + throw new NotFoundException('Course not found'); } - async findOne( - userId: string, - role: Role, - options: FindOneOptions - ): Promise { - const baseWhere = options.where as FindOptionsWhere; - const whereCondition = this.buildWhereCondition(userId, role, baseWhere); - - const course = await this.courseRepository.findOne({ - where: whereCondition, - relations: { - teacher: true, - category: true - } - }); - - if (!course) { - throw new NotFoundException("Course not found"); - } - - return course; + return course; + } + + async create( + userId: string, + createCourseDto: CreateCourseDto, + ): Promise { + try { + const course = this.courseRepository.create({ + ...createCourseDto, + teacher: { id: userId }, + category: { id: createCourseDto.categoryId }, + }); + + return this.courseRepository.save(course); + } catch (error) { + if (error instanceof Error) { + throw new BadRequestException(error.message); + } } - - - async create(userId: string, createCourseDto: CreateCourseDto): Promise { - try { - const course = this.courseRepository.create({ - ...createCourseDto, - teacher: { id: userId }, - category: { id: createCourseDto.categoryId } - }); - - return this.courseRepository.save(course); - } catch (error) { - if (error instanceof Error) { - throw new BadRequestException(error.message); - } - } - } - async update( - id: string, - userId: string, - role: Role, - updateCourseDto: UpdateCourseDto - ): Promise { - const existingCourse = await this.courseRepository.findOne({ - where: { id }, - relations: { - teacher: true, - category: true - } - }); - - if (!existingCourse) { - throw new NotFoundException('Course not found'); - } - - await this.validateUpdatePermissions(existingCourse, userId, role); - this.validateStatusTransition(existingCourse.status, updateCourseDto.status); - - const updatedCourse = await this.courseRepository.save({ - ...existingCourse, - ...updateCourseDto, - category: { id: updateCourseDto.categoryId } - }); - - return updatedCourse; + } + async update( + id: string, + userId: string, + role: Role, + updateCourseDto: UpdateCourseDto, + ): Promise { + const existingCourse = await this.courseRepository.findOne({ + where: { id }, + relations: { + teacher: true, + category: true, + }, + }); + + if (!existingCourse) { + throw new NotFoundException('Course not found'); } - async delete(id: string, userId: string, role: Role): Promise { - if (role === Role.TEACHER) - await this.checkOwnership(id, userId); - try { - await this.courseRepository.delete(id); - } catch (error) { - if (error instanceof Error) - throw new NotFoundException("Course not found"); - } + await this.validateUpdatePermissions(existingCourse, userId, role); + this.validateStatusTransition( + existingCourse.status, + updateCourseDto.status, + ); + + const updatedCourse = await this.courseRepository.save({ + ...existingCourse, + ...updateCourseDto, + category: { id: updateCourseDto.categoryId }, + }); + + return updatedCourse; + } + + async delete(id: string, userId: string, role: Role): Promise { + if (role === Role.TEACHER) await this.checkOwnership(id, userId); + try { + await this.courseRepository.delete(id); + } catch (error) { + if (error instanceof Error) + throw new NotFoundException('Course not found'); } - - private async checkOwnership(id: string, userId: string): Promise { - const course = await this.courseRepository.findOne({ where: { id } }); - if (course.teacher.id !== userId) - throw new BadRequestException("You don't own this course"); + } + + private async checkOwnership(id: string, userId: string): Promise { + const course = await this.courseRepository.findOne({ where: { id } }); + if (course.teacher.id !== userId) + throw new BadRequestException("You don't own this course"); + } + + private buildWhereCondition( + userId: string, + role: Role, + baseCondition: FindOptionsWhere = {}, + ): FindOptionsWhere | FindOptionsWhere[] { + const conditions: Record< + Role, + () => FindOptionsWhere | FindOptionsWhere[] + > = { + [Role.STUDENT]: () => ({ + ...baseCondition, + status: CourseStatus.PUBLISHED, + }), + [Role.TEACHER]: () => [ + { + ...baseCondition, + status: CourseStatus.PUBLISHED, + }, + { + ...baseCondition, + teacher: { id: userId }, + }, + ], + [Role.ADMIN]: () => baseCondition, + }; + + const buildCondition = conditions[role]; + + if (!buildCondition) { + throw new BadRequestException('Invalid role'); } - private buildWhereCondition( - userId: string, - role: Role, - baseCondition: FindOptionsWhere = {} - ): FindOptionsWhere | FindOptionsWhere[] { - const conditions: Record FindOptionsWhere | FindOptionsWhere[]> = { - [Role.STUDENT]: () => ({ - ...baseCondition, - status: CourseStatus.PUBLISHED - }), - [Role.TEACHER]: () => [ - { - ...baseCondition, - status: CourseStatus.PUBLISHED - }, - { - ...baseCondition, - teacher: { id: userId } - } - ], - [Role.ADMIN]: () => baseCondition - }; - - const buildCondition = conditions[role]; - - if (!buildCondition) { - throw new BadRequestException("Invalid role"); + return buildCondition(); + } + + private async validateUpdatePermissions( + course: Course, + userId: string, + role: Role, + ): Promise { + switch (role) { + case Role.TEACHER: + if (course.teacher.id !== userId) { + throw new ForbiddenException('You can only update your own courses'); } + break; - return buildCondition(); - } - - private async validateUpdatePermissions( - course: Course, - userId: string, - role: Role - ): Promise { - switch (role) { - case Role.TEACHER: - if (course.teacher.id !== userId) { - throw new ForbiddenException('You can only update your own courses'); - } - break; - - case Role.ADMIN: - if (course.status !== CourseStatus.DRAFT) { - throw new BadRequestException('Admin can only update courses in draft status'); - } - break; - - default: - throw new BadRequestException('Invalid role'); + case Role.ADMIN: + if (course.status !== CourseStatus.DRAFT) { + throw new BadRequestException( + 'Admin can only update courses in draft status', + ); } - } - - - private validateStatusTransition(currentStatus: CourseStatus, newStatus?: CourseStatus): void { - if (!newStatus) return; + break; - if (currentStatus !== CourseStatus.DRAFT && newStatus === CourseStatus.DRAFT) { - throw new BadRequestException( - `Cannot change status back to draft when current status is ${currentStatus}` - ); - } + default: + throw new BadRequestException('Invalid role'); + } + } + + private validateStatusTransition( + currentStatus: CourseStatus, + newStatus?: CourseStatus, + ): void { + if (!newStatus) return; + + if ( + currentStatus !== CourseStatus.DRAFT && + newStatus === CourseStatus.DRAFT + ) { + throw new BadRequestException( + `Cannot change status back to draft when current status is ${currentStatus}`, + ); } + } } diff --git a/src/course/dtos/create-course.dto.ts b/src/course/dtos/create-course.dto.ts index 5fb1389..4a7bd2c 100644 --- a/src/course/dtos/create-course.dto.ts +++ b/src/course/dtos/create-course.dto.ts @@ -1,74 +1,77 @@ - -import { ApiProperty } from "@nestjs/swagger"; -import { IsEnum, IsNotEmpty } from "class-validator"; -import { CourseLevel,CourseStatus } from "src/shared/enums/index"; +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNotEmpty } from 'class-validator'; +import { CourseLevel, CourseStatus } from 'src/shared/enums/index'; export class CreateCourseDto { - @IsNotEmpty() - @ApiProperty({ - description: 'Course title', - type: String, - example: 'Introduction to Programming', - }) - title: string; + @IsNotEmpty() + @ApiProperty({ + description: 'Course title', + type: String, + example: 'Introduction to Programming', + }) + title: string; - @IsNotEmpty() - @ApiProperty({ - description: 'Course description', - type: String, - example: 'This course is an introduction to programming', - }) - description: string; + @IsNotEmpty() + @ApiProperty({ + description: 'Course description', + type: String, + example: 'This course is an introduction to programming', + }) + description: string; - @IsNotEmpty() - @ApiProperty({ - description: 'Category Id', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }) - categoryId: string; + @IsNotEmpty() + @ApiProperty({ + description: 'Category Id', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + categoryId: string; - @IsNotEmpty() - @ApiProperty({ - description: 'Course thumbnail', - type: String, - example: 'https://www.example.com/thumbnail.jpg', - }) - thumbnail: string; + @IsNotEmpty() + @ApiProperty({ + description: 'Course thumbnail', + type: String, + example: 'https://www.example.com/thumbnail.jpg', + }) + thumbnail: string; - @IsNotEmpty() - @ApiProperty({ - description: 'Course duration', - type: Number, - example: 60, - }) - duration: number; + @IsNotEmpty() + @ApiProperty({ + description: 'Course duration', + type: Number, + example: 60, + }) + duration: number; - @IsNotEmpty() - @IsEnum(CourseLevel, {message: `Invalid level. Level should be either ${CourseLevel.BEGINNER}, ${CourseLevel.INTERMEDIATE}, or ${CourseLevel.ADVANCED}`}) - @ApiProperty({ - description: 'Course level', - type: String, - example: CourseLevel.BEGINNER, - enum: CourseLevel, - }) - level: CourseLevel; + @IsNotEmpty() + @IsEnum(CourseLevel, { + message: `Invalid level. Level should be either ${CourseLevel.BEGINNER}, ${CourseLevel.INTERMEDIATE}, or ${CourseLevel.ADVANCED}`, + }) + @ApiProperty({ + description: 'Course level', + type: String, + example: CourseLevel.BEGINNER, + enum: CourseLevel, + }) + level: CourseLevel; - @IsNotEmpty() - @ApiProperty({ - description: 'Course price', - type: Number, - example: 100, - }) - price: number; + @IsNotEmpty() + @ApiProperty({ + description: 'Course price', + type: Number, + example: 100, + }) + price: number; - @IsNotEmpty() - @IsEnum(CourseStatus, {message: `Invalid status. Status should be either ${CourseStatus.DRAFT}, ${CourseStatus.PUBLISHED}, or ${CourseStatus.ARCHIVED}`}) - @IsNotEmpty() - @ApiProperty({ - description: 'Course status', - type: String, - example: CourseStatus.DRAFT, - enum: CourseStatus, - }) - status: CourseStatus; -} \ No newline at end of file + @IsNotEmpty() + @IsEnum(CourseStatus, { + message: `Invalid status. Status should be either ${CourseStatus.DRAFT}, ${CourseStatus.PUBLISHED}, or ${CourseStatus.ARCHIVED}`, + }) + @IsNotEmpty() + @ApiProperty({ + description: 'Course status', + type: String, + example: CourseStatus.DRAFT, + enum: CourseStatus, + }) + status: CourseStatus; +} diff --git a/src/course/dtos/index.ts b/src/course/dtos/index.ts index bbbc275..07fdde7 100644 --- a/src/course/dtos/index.ts +++ b/src/course/dtos/index.ts @@ -1,3 +1,3 @@ export * from './create-course.dto'; export * from './update-course.dto'; -export * from './course-response.dto'; \ No newline at end of file +export * from './course-response.dto'; diff --git a/src/course/dtos/update-course.dto.ts b/src/course/dtos/update-course.dto.ts index eca89ee..3fdbb91 100644 --- a/src/course/dtos/update-course.dto.ts +++ b/src/course/dtos/update-course.dto.ts @@ -1,5 +1,4 @@ -import { PartialType } from "@nestjs/swagger"; -import { CreateCourseDto } from "./index"; +import { PartialType } from '@nestjs/swagger'; +import { CreateCourseDto } from './index'; export class UpdateCourseDto extends PartialType(CreateCourseDto) {} - diff --git a/src/database/database.module.ts b/src/database/database.module.ts index b0af886..857bc80 100644 --- a/src/database/database.module.ts +++ b/src/database/database.module.ts @@ -1,8 +1,8 @@ -import { Module } from "@nestjs/common"; -import { databaseProviders } from "./database.providers"; +import { Module } from '@nestjs/common'; +import { databaseProviders } from './database.providers'; @Module({ - providers: [...databaseProviders], - exports: [...databaseProviders] + providers: [...databaseProviders], + exports: [...databaseProviders], }) -export class DatabaseModule { } \ No newline at end of file +export class DatabaseModule {} diff --git a/src/database/database.providers.ts b/src/database/database.providers.ts index a425edd..3e390e4 100644 --- a/src/database/database.providers.ts +++ b/src/database/database.providers.ts @@ -1,11 +1,11 @@ -import { DataSource } from "typeorm"; -import { databaseConfig } from "../shared/configs/database.config"; +import { DataSource } from 'typeorm'; +import { databaseConfig } from '../shared/configs/database.config'; export const databaseProviders = [ - { - provide: "DataSource", - useFactory: async () => { - return await new DataSource(databaseConfig).initialize(); - }, + { + provide: 'DataSource', + useFactory: async () => { + return await new DataSource(databaseConfig).initialize(); }, -]; \ No newline at end of file + }, +]; diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index 9193ce6..d3b2363 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -1,12 +1,13 @@ -import { DataSourceOptions, DataSource } from 'typeorm'; import { ConfigService } from '@nestjs/config'; -import { GLOBAL_CONFIG } from '../constants/global-config.constant'; import 'dotenv/config'; -import { User } from 'src/user/user.entity'; import { Category } from 'src/category/category.entity'; -import { UserStreak } from 'src/user-streak/user-streak.entity'; -import { Course } from "src/course/course.entity"; +import { Chapter } from 'src/chapter/chapter.entity'; import { CourseModule } from 'src/course-module/course-module.entity'; +import { Course } from 'src/course/course.entity'; +import { UserStreak } from 'src/user-streak/user-streak.entity'; +import { User } from 'src/user/user.entity'; +import { DataSource, DataSourceOptions } from 'typeorm'; +import { GLOBAL_CONFIG } from '../constants/global-config.constant'; const configService = new ConfigService(); @@ -18,7 +19,7 @@ export const databaseConfig: DataSourceOptions = { password: configService.get(GLOBAL_CONFIG.DB_PASSWORD), database: configService.get(GLOBAL_CONFIG.DB_DATABASE), logging: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), - entities: [User, UserStreak, Category, Course, CourseModule], + entities: [User, UserStreak, Category, Course, CourseModule, Chapter], }; export default new DataSource(databaseConfig); diff --git a/src/shared/configs/dotenv.config.ts b/src/shared/configs/dotenv.config.ts index 24a71bd..fc95c5a 100644 --- a/src/shared/configs/dotenv.config.ts +++ b/src/shared/configs/dotenv.config.ts @@ -2,23 +2,23 @@ import * as Joi from 'joi'; import { Environment } from '../enums/environment.enum'; export const dotenvConfig = Joi.object({ - PORT: Joi.number().required(), - NODE_ENV: Joi.string() - .valid(...Object.values(Environment)) - .default(Environment.DEVELOPMENT), - IS_DEVELOPMENT: Joi.boolean().when('NODE_ENV', { - is: Joi.equal(Environment.DEVELOPMENT), - then: Joi.boolean().default(true), - otherwise: Joi.boolean().default(false), - }), - DB_HOST: Joi.string().required(), - DB_PORT: Joi.number().required(), - DB_USERNAME: Joi.string().required(), - DB_PASSWORD: Joi.string().required(), - DB_DATABASE: Joi.string().required(), - CORS_ALLOW_ORIGIN: Joi.string().required(), - JWT_ACCESS_SECRET: Joi.string().required(), - JWT_REFRESH_SECRET: Joi.string().required(), - JWT_ACCESS_EXPIRATION: Joi.string().required(), - JWT_REFRESH_EXPIRATION: Joi.string().required(), + PORT: Joi.number().required(), + NODE_ENV: Joi.string() + .valid(...Object.values(Environment)) + .default(Environment.DEVELOPMENT), + IS_DEVELOPMENT: Joi.boolean().when('NODE_ENV', { + is: Joi.equal(Environment.DEVELOPMENT), + then: Joi.boolean().default(true), + otherwise: Joi.boolean().default(false), + }), + DB_HOST: Joi.string().required(), + DB_PORT: Joi.number().required(), + DB_USERNAME: Joi.string().required(), + DB_PASSWORD: Joi.string().required(), + DB_DATABASE: Joi.string().required(), + CORS_ALLOW_ORIGIN: Joi.string().required(), + JWT_ACCESS_SECRET: Joi.string().required(), + JWT_REFRESH_SECRET: Joi.string().required(), + JWT_ACCESS_EXPIRATION: Joi.string().required(), + JWT_REFRESH_EXPIRATION: Joi.string().required(), }); diff --git a/src/shared/constants/global-config.constant.ts b/src/shared/constants/global-config.constant.ts index 4127d1a..d3d5d90 100644 --- a/src/shared/constants/global-config.constant.ts +++ b/src/shared/constants/global-config.constant.ts @@ -1,15 +1,15 @@ export const GLOBAL_CONFIG = { - PORT: "PORT", - NODE_ENV: "NODE_ENV", - IS_DEVELOPMENT: "IS_DEVELOPMENT", - DB_HOST: "DB_HOST", - DB_PORT: "DB_PORT", - DB_USERNAME: "DB_USERNAME", - DB_PASSWORD: "DB_PASSWORD", - DB_DATABASE: "DB_DATABASE", - CORS_ALLOW_ORIGIN: "CORS_ALLOW_ORIGIN", - JWT_ACCESS_SECRET: "JWT_ACCESS_SECRET", - JWT_REFRESH_SECRET: "JWT_REFRESH_SECRET", - JWT_ACCESS_EXPIRATION: "JWT_ACCESS_EXPIRATION", - JWT_REFRESH_EXPIRATION: "JWT_REFRESH_EXPIRATION", -}; \ No newline at end of file + PORT: 'PORT', + NODE_ENV: 'NODE_ENV', + IS_DEVELOPMENT: 'IS_DEVELOPMENT', + DB_HOST: 'DB_HOST', + DB_PORT: 'DB_PORT', + DB_USERNAME: 'DB_USERNAME', + DB_PASSWORD: 'DB_PASSWORD', + DB_DATABASE: 'DB_DATABASE', + CORS_ALLOW_ORIGIN: 'CORS_ALLOW_ORIGIN', + JWT_ACCESS_SECRET: 'JWT_ACCESS_SECRET', + JWT_REFRESH_SECRET: 'JWT_REFRESH_SECRET', + JWT_ACCESS_EXPIRATION: 'JWT_ACCESS_EXPIRATION', + JWT_REFRESH_EXPIRATION: 'JWT_REFRESH_EXPIRATION', +}; diff --git a/src/shared/decorators/public.decorator.ts b/src/shared/decorators/public.decorator.ts index f0d4ec5..7659150 100644 --- a/src/shared/decorators/public.decorator.ts +++ b/src/shared/decorators/public.decorator.ts @@ -1,5 +1,5 @@ -import { CustomDecorator, SetMetadata } from "@nestjs/common"; +import { CustomDecorator, SetMetadata } from '@nestjs/common'; -export const IS_PUBLIC_KEY = "isPublic"; +export const IS_PUBLIC_KEY = 'isPublic'; export const Public = (): CustomDecorator => - SetMetadata(IS_PUBLIC_KEY, true); + SetMetadata(IS_PUBLIC_KEY, true); diff --git a/src/shared/decorators/role.decorator.ts b/src/shared/decorators/role.decorator.ts index 45788dc..a2c709a 100644 --- a/src/shared/decorators/role.decorator.ts +++ b/src/shared/decorators/role.decorator.ts @@ -2,4 +2,4 @@ import { SetMetadata } from '@nestjs/common'; import { Role } from '../enums/roles.enum'; export const ROLES_KEY = 'roles'; -export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles); \ No newline at end of file +export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles); diff --git a/src/shared/enums/course-level.enum.ts b/src/shared/enums/course-level.enum.ts index d56baa3..b1bbf11 100644 --- a/src/shared/enums/course-level.enum.ts +++ b/src/shared/enums/course-level.enum.ts @@ -1,5 +1,5 @@ export enum CourseLevel { - BEGINNER = "beginner", - INTERMEDIATE = "intermediate", - ADVANCED = "advanced", + BEGINNER = 'beginner', + INTERMEDIATE = 'intermediate', + ADVANCED = 'advanced', } diff --git a/src/shared/enums/course-status.enum.ts b/src/shared/enums/course-status.enum.ts index 291c29d..9fd3908 100644 --- a/src/shared/enums/course-status.enum.ts +++ b/src/shared/enums/course-status.enum.ts @@ -1,5 +1,5 @@ export enum CourseStatus { - DRAFT = "draft", - PUBLISHED = "published", - ARCHIVED = "archived", + DRAFT = 'draft', + PUBLISHED = 'published', + ARCHIVED = 'archived', } diff --git a/src/shared/enums/environment.enum.ts b/src/shared/enums/environment.enum.ts index 1ff20ee..60d3494 100644 --- a/src/shared/enums/environment.enum.ts +++ b/src/shared/enums/environment.enum.ts @@ -1,4 +1,4 @@ export enum Environment { - DEVELOPMENT = "development", - PRODUCTION = "production", -} \ No newline at end of file + DEVELOPMENT = 'development', + PRODUCTION = 'production', +} diff --git a/src/shared/enums/roles.enum.ts b/src/shared/enums/roles.enum.ts index 8069a17..cefe86d 100644 --- a/src/shared/enums/roles.enum.ts +++ b/src/shared/enums/roles.enum.ts @@ -1,10 +1,10 @@ export enum Role { - ADMIN = "admin", - STUDENT = "student", - TEACHER = "teacher", + ADMIN = 'admin', + STUDENT = 'student', + TEACHER = 'teacher', } export enum AvailableRoles { - STUDENT = Role.STUDENT, - TEACHER = Role.TEACHER, -} \ No newline at end of file + STUDENT = Role.STUDENT, + TEACHER = Role.TEACHER, +} diff --git a/src/shared/guards/role.guard.ts b/src/shared/guards/role.guard.ts index 336d042..39dcde8 100644 --- a/src/shared/guards/role.guard.ts +++ b/src/shared/guards/role.guard.ts @@ -6,16 +6,15 @@ import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request. @Injectable() export class RolesGuard implements CanActivate { - constructor(private reflector: Reflector) { } + constructor(private reflector: Reflector) {} - canActivate(context: ExecutionContext): boolean { - const requiredRoles = this.reflector.getAllAndOverride(ROLES_KEY, [ - context.getHandler(), - context.getClass(), - ]); - if (!requiredRoles) - return true; - const { user } = context.switchToHttp().getRequest(); - return requiredRoles.some((role) => role === user.role); - } + canActivate(context: ExecutionContext): boolean { + const requiredRoles = this.reflector.getAllAndOverride(ROLES_KEY, [ + context.getHandler(), + context.getClass(), + ]); + if (!requiredRoles) return true; + const { user } = context.switchToHttp().getRequest(); + return requiredRoles.some((role) => role === user.role); + } } diff --git a/src/user-streak/dtos/user-streak-response.dto.ts b/src/user-streak/dtos/user-streak-response.dto.ts index b2facf1..a8ae2f7 100644 --- a/src/user-streak/dtos/user-streak-response.dto.ts +++ b/src/user-streak/dtos/user-streak-response.dto.ts @@ -1,63 +1,63 @@ -import { ApiProperty } from "@nestjs/swagger" -import { UserStreak } from "../user-streak.entity"; +import { ApiProperty } from '@nestjs/swagger'; +import { UserStreak } from '../user-streak.entity'; export class UserStreakResponseDto { - @ApiProperty({ - description: 'User streak ID', - type: String, - example: '123e4567-e89b-12d3-a456-426614174000', - }) - id: string; - - @ApiProperty({ - description: 'User current streak', - type: Number, - example: 0, - }) - currentStreak: number; - - @ApiProperty({ - description: 'User longest streak', - type: Number, - example: 0, - }) - longestStreak: number; - - @ApiProperty({ - description: 'User last activity date', - type: Date, - example: '2021-08-01T00:00:00.000Z', - }) - lastActivityDate: Date; - - @ApiProperty({ - description: 'User created date', - type: Date, - example: '2021-08-01T00:00:00.000Z', - }) - createdAt: Date; - - @ApiProperty({ - description: 'User updated date', - type: Date, - example: '2021-08-01T00:00:00.000Z', - }) - updatedAt: Date; - - @ApiProperty({ - description: 'User ID', - type: String, - example: '123e4567-e89b-12d3-a456-426614174000', - }) - userId: string; - - constructor(userStreak: UserStreak) { - this.id = userStreak.id; - this.currentStreak = userStreak.currentStreak; - this.longestStreak = userStreak.longestStreak; - this.lastActivityDate = userStreak.lastActivityDate; - this.createdAt = userStreak.createdAt; - this.updatedAt = userStreak.updatedAt; - this.userId = userStreak.user.id; - } -} \ No newline at end of file + @ApiProperty({ + description: 'User streak ID', + type: String, + example: '123e4567-e89b-12d3-a456-426614174000', + }) + id: string; + + @ApiProperty({ + description: 'User current streak', + type: Number, + example: 0, + }) + currentStreak: number; + + @ApiProperty({ + description: 'User longest streak', + type: Number, + example: 0, + }) + longestStreak: number; + + @ApiProperty({ + description: 'User last activity date', + type: Date, + example: '2021-08-01T00:00:00.000Z', + }) + lastActivityDate: Date; + + @ApiProperty({ + description: 'User created date', + type: Date, + example: '2021-08-01T00:00:00.000Z', + }) + createdAt: Date; + + @ApiProperty({ + description: 'User updated date', + type: Date, + example: '2021-08-01T00:00:00.000Z', + }) + updatedAt: Date; + + @ApiProperty({ + description: 'User ID', + type: String, + example: '123e4567-e89b-12d3-a456-426614174000', + }) + userId: string; + + constructor(userStreak: UserStreak) { + this.id = userStreak.id; + this.currentStreak = userStreak.currentStreak; + this.longestStreak = userStreak.longestStreak; + this.lastActivityDate = userStreak.lastActivityDate; + this.createdAt = userStreak.createdAt; + this.updatedAt = userStreak.updatedAt; + this.userId = userStreak.user.id; + } +} diff --git a/src/user-streak/user-streak.controller.ts b/src/user-streak/user-streak.controller.ts index 013fb3d..2c8668d 100644 --- a/src/user-streak/user-streak.controller.ts +++ b/src/user-streak/user-streak.controller.ts @@ -1,65 +1,65 @@ import { - Controller, - Injectable, - Get, - Req, - Patch, - HttpStatus, - HttpCode, + Controller, + Get, + HttpCode, + HttpStatus, + Injectable, + Patch, + Req, } from '@nestjs/common'; -import { ApiTags, ApiResponse, ApiBearerAuth } from '@nestjs/swagger'; -import { UserStreakService } from './user-streak.service'; -import { UserStreak } from './user-streak.entity'; +import { ApiBearerAuth, ApiResponse, ApiTags } from '@nestjs/swagger'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; -import { Role } from 'src/shared/enums/roles.enum'; import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums/roles.enum'; import { UserStreakResponseDto } from './dtos/user-streak-response.dto'; +import { UserStreak } from './user-streak.entity'; +import { UserStreakService } from './user-streak.service'; @Controller('user-streak') @ApiTags('User Streak') @ApiBearerAuth() @Injectable() export class UserStreakController { - constructor(private readonly userStreakService: UserStreakService) { } + constructor(private readonly userStreakService: UserStreakService) {} - @Get() - @Roles(Role.ADMIN) - @ApiResponse({ - status: HttpStatus.OK, - type: UserStreak, - description: 'Get all user streaks', - isArray: true, - }) - async findAll(): Promise { - const userStreaks = await this.userStreakService.findAll(); - return userStreaks.map( - (userStreak) => new UserStreakResponseDto(userStreak), - ); - } + @Get() + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + type: UserStreak, + description: 'Get all user streaks', + isArray: true, + }) + async findAll(): Promise { + const userStreaks = await this.userStreakService.findAll(); + return userStreaks.map( + (userStreak) => new UserStreakResponseDto(userStreak), + ); + } - @Get('profile') - @ApiResponse({ - status: HttpStatus.OK, - type: UserStreak, - description: 'Get user streak', - }) - async findOne( - @Req() request: AuthenticatedRequest, - ): Promise { - const userStreak = await this.userStreakService.findOne({ - where: { user: { id: request.user.id } }, - relations: { user: true }, - }); - return new UserStreakResponseDto(userStreak); - } + @Get('profile') + @ApiResponse({ + status: HttpStatus.OK, + type: UserStreak, + description: 'Get user streak', + }) + async findOne( + @Req() request: AuthenticatedRequest, + ): Promise { + const userStreak = await this.userStreakService.findOne({ + where: { user: { id: request.user.id } }, + relations: { user: true }, + }); + return new UserStreakResponseDto(userStreak); + } - @Patch() - @ApiResponse({ - status: HttpStatus.NO_CONTENT, - description: 'Update user streak', - }) - @HttpCode(HttpStatus.NO_CONTENT) - async update(@Req() request: AuthenticatedRequest): Promise { - return await this.userStreakService.update(request.user.id); - } + @Patch() + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Update user streak', + }) + @HttpCode(HttpStatus.NO_CONTENT) + async update(@Req() request: AuthenticatedRequest): Promise { + return await this.userStreakService.update(request.user.id); + } } diff --git a/src/user-streak/user-streak.entity.ts b/src/user-streak/user-streak.entity.ts index 74897fd..882fc3e 100644 --- a/src/user-streak/user-streak.entity.ts +++ b/src/user-streak/user-streak.entity.ts @@ -1,42 +1,50 @@ -import { Entity, Column, PrimaryGeneratedColumn, OneToOne, JoinColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"; -import { User } from "src/user/user.entity"; +import { User } from 'src/user/user.entity'; +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + OneToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; @Entity() export class UserStreak { - @PrimaryGeneratedColumn("uuid") - id: string; + @PrimaryGeneratedColumn('uuid') + id: string; - @OneToOne(() => User) - @JoinColumn() - user: User; + @OneToOne(() => User) + @JoinColumn() + user: User; - @Column({ - default: 0, - nullable: false, - }) - currentStreak: number; + @Column({ + default: 0, + nullable: false, + }) + currentStreak: number; - @Column({ - default: 0, - nullable: false, - }) - longestStreak: number; + @Column({ + default: 0, + nullable: false, + }) + longestStreak: number; - @Column({ - type: "timestamp with time zone", - default: () => "CURRENT_TIMESTAMP", - }) - lastActivityDate: Date; + @Column({ + type: 'timestamp with time zone', + default: () => 'CURRENT_TIMESTAMP', + }) + lastActivityDate: Date; - @CreateDateColumn({ - type: "timestamp with time zone", - nullable: false, - }) - createdAt: Date; + @CreateDateColumn({ + type: 'timestamp with time zone', + nullable: false, + }) + createdAt: Date; - @UpdateDateColumn({ - type: "timestamp with time zone", - nullable: false, - }) - updatedAt: Date; -} \ No newline at end of file + @UpdateDateColumn({ + type: 'timestamp with time zone', + nullable: false, + }) + updatedAt: Date; +} diff --git a/src/user-streak/user-streak.module.ts b/src/user-streak/user-streak.module.ts index ea0fa1a..c650bb3 100644 --- a/src/user-streak/user-streak.module.ts +++ b/src/user-streak/user-streak.module.ts @@ -1,18 +1,13 @@ -import { Module } from "@nestjs/common"; -import { UserStreakController } from "./user-streak.controller"; -import { UserStreakService } from "./user-streak.service"; -import { DatabaseModule } from "src/database/database.module"; -import { userStreakProviders } from "./user-streak.providers"; +import { Module } from '@nestjs/common'; +import { DatabaseModule } from 'src/database/database.module'; +import { UserStreakController } from './user-streak.controller'; +import { userStreakProviders } from './user-streak.providers'; +import { UserStreakService } from './user-streak.service'; @Module({ - imports: [ - DatabaseModule, - ], - controllers: [UserStreakController], - providers: [ - ...userStreakProviders, - UserStreakService - ], - exports: [UserStreakService], + imports: [DatabaseModule], + controllers: [UserStreakController], + providers: [...userStreakProviders, UserStreakService], + exports: [UserStreakService], }) -export class UserStreakModule { } \ No newline at end of file +export class UserStreakModule {} diff --git a/src/user-streak/user-streak.providers.ts b/src/user-streak/user-streak.providers.ts index 346220b..e3e719c 100644 --- a/src/user-streak/user-streak.providers.ts +++ b/src/user-streak/user-streak.providers.ts @@ -1,8 +1,11 @@ -import { DataSource } from "typeorm"; -import { UserStreak } from "./user-streak.entity"; +import { DataSource } from 'typeorm'; +import { UserStreak } from './user-streak.entity'; -export const userStreakProviders = [{ +export const userStreakProviders = [ + { provide: 'UserStreakRepository', - useFactory: (dataSource: DataSource) => dataSource.getRepository(UserStreak), + useFactory: (dataSource: DataSource) => + dataSource.getRepository(UserStreak), inject: ['DataSource'], -}]; \ No newline at end of file + }, +]; diff --git a/src/user-streak/user-streak.service.ts b/src/user-streak/user-streak.service.ts index a07697f..36922d0 100644 --- a/src/user-streak/user-streak.service.ts +++ b/src/user-streak/user-streak.service.ts @@ -1,68 +1,72 @@ -import { Injectable, Inject, NotFoundException, InternalServerErrorException } from '@nestjs/common'; -import { Repository, FindOneOptions, FindOptionsWhere } from 'typeorm'; +import { + Inject, + Injectable, + InternalServerErrorException, + NotFoundException, +} from '@nestjs/common'; +import { FindOneOptions, FindOptionsWhere, Repository } from 'typeorm'; import { UserStreak } from './user-streak.entity'; @Injectable() export class UserStreakService { - constructor( - @Inject('UserStreakRepository') - private readonly userStreakRepository: Repository, - ) { } + constructor( + @Inject('UserStreakRepository') + private readonly userStreakRepository: Repository, + ) {} - async create(userId: string): Promise { - return await this.userStreakRepository.save({ - user: { id: userId }, - }) - } + async create(userId: string): Promise { + return await this.userStreakRepository.save({ + user: { id: userId }, + }); + } - async findAll(): Promise { - return this.userStreakRepository.find(); - } + async findAll(): Promise { + return this.userStreakRepository.find(); + } - async findOne(options: FindOneOptions): Promise { - const userStreak = await this.userStreakRepository.findOne(options); - if (!userStreak) - throw new NotFoundException('User streak not found'); - return userStreak; - } + async findOne(options: FindOneOptions): Promise { + const userStreak = await this.userStreakRepository.findOne(options); + if (!userStreak) throw new NotFoundException('User streak not found'); + return userStreak; + } - async delete(options: FindOptionsWhere): Promise { - try { - await this.userStreakRepository.delete(options); - } catch (error) { - if (error instanceof Error) { - throw new NotFoundException('User streak not found'); - } - } + async delete(options: FindOptionsWhere): Promise { + try { + await this.userStreakRepository.delete(options); + } catch (error) { + if (error instanceof Error) { + throw new NotFoundException('User streak not found'); + } } + } - async update(userId: string): Promise { - try { - const streak = await this.findOne({ where: { user: { id: userId } } }); - const currentDate = new Date(); - const diff = - Math.abs( - currentDate.getTime() - new Date(streak.lastActivityDate).getTime(), - ) / - (1000 * 60 * 60 * 24); - if (diff > 1) streak.currentStreak = 0; - else streak.currentStreak++; - if (streak.currentStreak > streak.longestStreak) - streak.longestStreak = streak.currentStreak; - await this.userStreakRepository.update( - { - user: { id: userId }, - }, - { - currentStreak: streak.currentStreak, - longestStreak: streak.longestStreak, - lastActivityDate: currentDate, - }, - ); - } catch (error) { - if (error instanceof Error) { - throw new InternalServerErrorException(error.message); - } - } + async update(userId: string): Promise { + try { + const streak = await this.findOne({ where: { user: { id: userId } } }); + const currentDate = new Date(); + const diff = + Math.abs( + currentDate.getTime() - new Date(streak.lastActivityDate).getTime(), + ) / + (1000 * 60 * 60 * 24); + if (diff > 1) streak.currentStreak = 0; + else streak.currentStreak++; + if (streak.currentStreak > streak.longestStreak) + streak.longestStreak = streak.currentStreak; + await this.userStreakRepository.update( + { + user: { id: userId }, + }, + { + currentStreak: streak.currentStreak, + longestStreak: streak.longestStreak, + lastActivityDate: currentDate, + }, + ); + } catch (error) { + if (error instanceof Error) { + throw new InternalServerErrorException(error.message); + } } + } } diff --git a/src/user/dtos/update-user.dto.ts b/src/user/dtos/update-user.dto.ts index f8ad7e7..e89fde4 100644 --- a/src/user/dtos/update-user.dto.ts +++ b/src/user/dtos/update-user.dto.ts @@ -1,23 +1,23 @@ -import { IsString, IsOptional, IsStrongPassword } from "class-validator" -import { ApiProperty } from "@nestjs/swagger"; +import { IsString, IsOptional, IsStrongPassword } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; export class UpdateUserDto { - @IsString() - @IsOptional() - @ApiProperty({ - description: 'User email', - type: String, - example: 'johndoe', - }) - username?: string; + @IsString() + @IsOptional() + @ApiProperty({ + description: 'User email', + type: String, + example: 'johndoe', + }) + username?: string; - @IsString() - @IsOptional() - @IsStrongPassword() - @ApiProperty({ - description: 'New User Password', - type: String, - example: 'P@ssw0rd!', - }) - password?: string; -} \ No newline at end of file + @IsString() + @IsOptional() + @IsStrongPassword() + @ApiProperty({ + description: 'New User Password', + type: String, + example: 'P@ssw0rd!', + }) + password?: string; +} diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index 7f69d24..5faf545 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -1,56 +1,61 @@ -import { Entity, OneToMany } from "typeorm"; -import { Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn } from "typeorm"; -import { Role } from "src/shared/enums/roles.enum"; -import { Course } from "src/course/course.entity"; +import { Entity, OneToMany } from 'typeorm'; +import { + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, +} from 'typeorm'; +import { Role } from 'src/shared/enums/roles.enum'; +import { Course } from 'src/course/course.entity'; @Entity() export class User { - @PrimaryGeneratedColumn("uuid") - id: string; - - @Column({ - nullable: false, - unique: true, - }) - username: string; - - @Column({ - nullable: false, - }) - fullname: string; - - @Column({ - type: "enum", - enum: Role, - nullable: false, - default: Role.STUDENT, - }) - role: Role; - - @Column({ - nullable: false, - unique: true, - }) - password: string; - - @Column({ - nullable: false, - unique: true, - }) - email: string; - - @OneToMany(() => Course, (course) => course.teacher) - courses: Course[]; - - @CreateDateColumn({ - type: "timestamp with time zone", - nullable: false, - }) - createdAt: Date; - - @UpdateDateColumn({ - type: "timestamp with time zone", - nullable: false, - }) - updatedAt: Date; -} \ No newline at end of file + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ + nullable: false, + unique: true, + }) + username: string; + + @Column({ + nullable: false, + }) + fullname: string; + + @Column({ + type: 'enum', + enum: Role, + nullable: false, + default: Role.STUDENT, + }) + role: Role; + + @Column({ + nullable: false, + unique: true, + }) + password: string; + + @Column({ + nullable: false, + unique: true, + }) + email: string; + + @OneToMany(() => Course, (course) => course.teacher) + courses: Course[]; + + @CreateDateColumn({ + type: 'timestamp with time zone', + nullable: false, + }) + createdAt: Date; + + @UpdateDateColumn({ + type: 'timestamp with time zone', + nullable: false, + }) + updatedAt: Date; +} diff --git a/src/user/user.providers.ts b/src/user/user.providers.ts index 5e892de..813fb78 100644 --- a/src/user/user.providers.ts +++ b/src/user/user.providers.ts @@ -1,8 +1,10 @@ -import { DataSource } from "typeorm"; -import { User } from "./user.entity"; +import { DataSource } from 'typeorm'; +import { User } from './user.entity'; -export const userProviders = [{ +export const userProviders = [ + { provide: 'UserRepository', useFactory: (dataSource: DataSource) => dataSource.getRepository(User), inject: ['DataSource'], -}]; \ No newline at end of file + }, +]; diff --git a/src/user/user.service.ts b/src/user/user.service.ts index fe3cd78..053eefc 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,8 +1,8 @@ import { - BadRequestException, - Inject, - Injectable, - NotFoundException, + BadRequestException, + Inject, + Injectable, + NotFoundException, } from '@nestjs/common'; import { hash } from 'argon2'; import { createPagination } from 'src/shared/pagination'; @@ -14,62 +14,62 @@ import { User } from './user.entity'; @Injectable() export class UserService { - constructor( - @Inject('UserRepository') - private readonly userRepository: Repository, - ) { } + constructor( + @Inject('UserRepository') + private readonly userRepository: Repository, + ) {} - async findAll({ - page = 1, - limit = 20, - search = '', - }: { - page?: number; - limit?: number; - search?: string; - }): Promise { - const { find } = await createPagination(this.userRepository, { - page, - limit, - }); + async findAll({ + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }): Promise { + const { find } = await createPagination(this.userRepository, { + page, + limit, + }); - const users = await find({ - where: { email: ILike(`%${search}%`) }, - }).run(); + const users = await find({ + where: { email: ILike(`%${search}%`) }, + }).run(); - return users; - } + return users; + } - async findOne(options: FindOneOptions): Promise { - const user = this.userRepository.findOne(options); - if (!user) throw new NotFoundException('User not found'); - return user; - } + async findOne(options: FindOneOptions): Promise { + const user = this.userRepository.findOne(options); + if (!user) throw new NotFoundException('User not found'); + return user; + } - async create(createUserDto: CreateUserDto): Promise { - try { - return this.userRepository.save(createUserDto); - } catch (error) { - if (error instanceof Error) throw new BadRequestException(error.message); - } + async create(createUserDto: CreateUserDto): Promise { + try { + return this.userRepository.save(createUserDto); + } catch (error) { + if (error instanceof Error) throw new BadRequestException(error.message); } + } - async update(id: string, updateUserDto: UpdateUserDto): Promise { - try { - if (updateUserDto.password) - updateUserDto.password = await hash(updateUserDto.password); - await this.userRepository.update(id, updateUserDto); - return await this.findOne({ where: { id } }); - } catch (error) { - if (error instanceof Error) throw new NotFoundException('User not found'); - } + async update(id: string, updateUserDto: UpdateUserDto): Promise { + try { + if (updateUserDto.password) + updateUserDto.password = await hash(updateUserDto.password); + await this.userRepository.update(id, updateUserDto); + return await this.findOne({ where: { id } }); + } catch (error) { + if (error instanceof Error) throw new NotFoundException('User not found'); } + } - async delete(id: string): Promise { - try { - await this.userRepository.delete(id); - } catch (error) { - if (error instanceof Error) throw new NotFoundException('User not found'); - } + async delete(id: string): Promise { + try { + await this.userRepository.delete(id); + } catch (error) { + if (error instanceof Error) throw new NotFoundException('User not found'); } + } } From 81cc1adcd730f6515e5e4331886c7b38697bdc86 Mon Sep 17 00:00:00 2001 From: Potsawee Date: Thu, 14 Nov 2024 17:17:36 +0700 Subject: [PATCH 030/155] create reward module service --- src/app.module.ts | 76 +++++++-------- src/reward/dtos/create-reward.dto.ts | 55 ++++++++++- src/reward/dtos/reward-response.dto.ts | 91 ++++++++++++++++++ src/reward/dtos/update-reward.dto.ts | 80 ++++++++++++++++ src/reward/enums/status.enum.ts | 4 + src/reward/enums/type.enum.ts | 5 + src/reward/reward.controllers.ts | 122 +++++++++++++++++++++++++ src/reward/reward.entity.ts | 38 ++++++-- src/reward/reward.module.ts | 13 +++ src/reward/reward.providers.ts | 10 ++ src/reward/reward.service.ts | 57 ++++++++++++ 11 files changed, 501 insertions(+), 50 deletions(-) create mode 100644 src/reward/dtos/reward-response.dto.ts create mode 100644 src/reward/dtos/update-reward.dto.ts create mode 100644 src/reward/enums/status.enum.ts create mode 100644 src/reward/enums/type.enum.ts create mode 100644 src/reward/reward.providers.ts diff --git a/src/app.module.ts b/src/app.module.ts index 0507d60..80187c1 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -17,45 +17,47 @@ import { AuthGuard } from './auth/auth.guard'; import { UserStreak } from './user-streak/user-streak.entity'; import { UserStreakModule } from './user-streak/user-streak.module'; import { RolesGuard } from './shared/guards/role.guard'; +import { RewardModule } from './reward/reward.module'; const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); @Module({ - imports: [ - forFeatures, - AuthModule, - ConfigModule.forRoot({ - isGlobal: true, - validationSchema: dotenvConfig, - }), - TypeOrmModule.forRootAsync({ - imports: [ConfigModule], - useFactory: (configService: ConfigService) => ({ - ...databaseConfig, - migrations: ["dist/database/migrations/*.js"], - migrationsRun: true, - synchronize: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), - }), - inject: [ConfigService], - }), - JwtModule.register({ - global: true, - }), - DatabaseModule, - UserModule, - UserStreakModule, - ], - controllers: [AppController], - providers: [ - AppService, - { - provide: APP_GUARD, - useClass: AuthGuard, - }, - { - provide: APP_GUARD, - useClass: RolesGuard, - } - ], + imports: [ + forFeatures, + AuthModule, + ConfigModule.forRoot({ + isGlobal: true, + validationSchema: dotenvConfig, + }), + TypeOrmModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + ...databaseConfig, + migrations: ['dist/database/migrations/*.js'], + migrationsRun: true, + synchronize: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), + }), + inject: [ConfigService], + }), + JwtModule.register({ + global: true, + }), + DatabaseModule, + UserModule, + UserStreakModule, + RewardModule, + ], + controllers: [AppController], + providers: [ + AppService, + { + provide: APP_GUARD, + useClass: AuthGuard, + }, + { + provide: APP_GUARD, + useClass: RolesGuard, + }, + ], }) -export class AppModule { } +export class AppModule {} diff --git a/src/reward/dtos/create-reward.dto.ts b/src/reward/dtos/create-reward.dto.ts index 0c1d4e5..c925e2a 100644 --- a/src/reward/dtos/create-reward.dto.ts +++ b/src/reward/dtos/create-reward.dto.ts @@ -1,34 +1,81 @@ import { + IsEnum, IsNotEmpty, IsNumber, IsOptional, IsString, } from '@nestjs/class-validator'; +import { Type } from '../enums/type.enum'; +import { Status } from '../enums/status.enum'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; export class CreateRewardDto { @IsString() @IsNotEmpty() + @ApiProperty({ + description: 'reward name', + type: String, + example: 'gift card', + }) name: string; @IsOptional() @IsString() + @ApiPropertyOptional({ + description: 'description of reward (optional)', + type: String, + example: 'get 15% of in next corse', + }) description?: string; @IsString() + @IsOptional() + @ApiPropertyOptional({ + description: 'thumbnail of reward (optional)', + type: String, + example: 'url.png', + }) thumnail: string; - @IsString() - type: string; + @IsEnum(Type, { + message: `Invalid type. Type should be ${Type.BADGE} ${Type.CERTIFICATE} or ${Type.ITEM}`, + }) + @IsNotEmpty() + @ApiProperty({ + description: 'type of reward', + type: String, + example: Type.BADGE, + enum: Type, + }) + type: Type.BADGE | Type.CERTIFICATE | Type.ITEM; @IsNumber() @IsNotEmpty() + @ApiProperty({ + description: 'how many points require for this reward', + type: Number, + example: 100, + }) points: number; @IsNumber() @IsNotEmpty() + @ApiProperty({ + description: 'how many reward left', + type: Number, + example: 5, + }) stock: number; - @IsString() + @IsEnum(Status, { + message: `Invalid status. Status should be ${Status.ACTIVE} or ${Status.INACTIVE}`, + }) @IsNotEmpty() - status: string; + @ApiProperty({ + description: 'status of reward', + type: String, + example: Status.INACTIVE, + enum: Status, + }) + status: Status.ACTIVE | Status.INACTIVE; } diff --git a/src/reward/dtos/reward-response.dto.ts b/src/reward/dtos/reward-response.dto.ts new file mode 100644 index 0000000..482b3f7 --- /dev/null +++ b/src/reward/dtos/reward-response.dto.ts @@ -0,0 +1,91 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { Status } from '../enums/status.enum'; +import { Type } from '../enums/type.enum'; +import { Reward } from '../reward.entity'; + +export class RewardResponseDto { + @ApiProperty({ + description: 'Reward ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + id: string; + + @ApiProperty({ + description: 'reward name', + type: String, + example: 'gift card', + }) + name: string; + + @ApiPropertyOptional({ + description: 'description of reward (optional)', + type: String, + example: 'get 15% of in next corse', + }) + description?: string; + + @ApiPropertyOptional({ + description: 'thumbnail of reward (optional)', + type: String, + example: 'url.png', + }) + thumbnail: string; + + @ApiProperty({ + description: 'type of reward', + type: String, + example: Type.BADGE, + enum: Type, + }) + type: Type; + + @ApiProperty({ + description: 'how many points require for this reward', + type: Number, + example: 100, + }) + points: number; + + @ApiProperty({ + description: 'how many reward left', + type: Number, + example: 5, + }) + stock: number; + + @ApiProperty({ + description: 'status of reward', + type: String, + example: Status.INACTIVE, + enum: Status, + }) + status: Status; + + @ApiProperty({ + description: 'created date', + type: Date, + example: new Date(), + }) + createdAt: Date; + + @ApiProperty({ + description: 'updated date', + type: Date, + example: new Date(), + }) + updatedAt: Date; + + constructor(reward: Reward) { + this.id = reward.id; + this.name = reward.name; + this.description = reward.description; + this.thumbnail = reward.thumbnail; + this.type = reward.type; + this.points = reward.points; + this.stock = reward.stock; + this.status = reward.status; + this.createdAt = reward.createdAt; + this.updatedAt = reward.updatedAt; + } +} diff --git a/src/reward/dtos/update-reward.dto.ts b/src/reward/dtos/update-reward.dto.ts new file mode 100644 index 0000000..e25cf21 --- /dev/null +++ b/src/reward/dtos/update-reward.dto.ts @@ -0,0 +1,80 @@ +import { + IsEnum, + IsNumber, + IsOptional, + IsString, +} from '@nestjs/class-validator'; +import { Type } from '../enums/type.enum'; +import { Status } from '../enums/status.enum'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +export class UpdateRewardDto { + @IsString() + @IsOptional() + @ApiPropertyOptional({ + description: 'reward name', + type: String, + example: 'gift card', + }) + name?: string; + + @IsOptional() + @IsString() + @ApiPropertyOptional({ + description: 'description of reward (optional)', + type: String, + example: 'get 15% of in next corse', + }) + description?: string; + + @IsString() + @IsOptional() + @ApiPropertyOptional({ + description: 'thumbnail of reward (optional)', + type: String, + example: 'url.png', + }) + thumnail?: string; + + @IsEnum(Type, { + message: `Invalid type. Type should be ${Type.BADGE} ${Type.CERTIFICATE} or ${Type.ITEM}`, + }) + @IsOptional() + @ApiPropertyOptional({ + description: 'type of reward', + type: String, + example: Type.BADGE, + enum: Type, + }) + type?: Type.BADGE | Type.CERTIFICATE | Type.ITEM; + + @IsNumber() + @IsOptional() + @ApiPropertyOptional({ + description: 'how many points require for this reward', + type: Number, + example: 100, + }) + points?: number; + + @IsNumber() + @IsOptional() + @ApiPropertyOptional({ + description: 'how many reward left', + type: Number, + example: 5, + }) + stock?: number; + + @IsEnum(Status, { + message: `Invalid status. Status should be ${Status.ACTIVE} or ${Status.INACTIVE}`, + }) + @IsOptional() + @ApiPropertyOptional({ + description: 'status of reward', + type: String, + example: Status.INACTIVE, + enum: Status, + }) + status?: Status.ACTIVE | Status.INACTIVE; +} diff --git a/src/reward/enums/status.enum.ts b/src/reward/enums/status.enum.ts new file mode 100644 index 0000000..78be8fd --- /dev/null +++ b/src/reward/enums/status.enum.ts @@ -0,0 +1,4 @@ +export enum Status { + ACTIVE = 'active', + INACTIVE = 'inactive', +} diff --git a/src/reward/enums/type.enum.ts b/src/reward/enums/type.enum.ts new file mode 100644 index 0000000..3848346 --- /dev/null +++ b/src/reward/enums/type.enum.ts @@ -0,0 +1,5 @@ +export enum Type { + BADGE = 'badge', + CERTIFICATE = 'certificate', + ITEM = 'item', +} diff --git a/src/reward/reward.controllers.ts b/src/reward/reward.controllers.ts index e69de29..b97cecc 100644 --- a/src/reward/reward.controllers.ts +++ b/src/reward/reward.controllers.ts @@ -0,0 +1,122 @@ +import { + Body, + Controller, + Get, + HttpCode, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Post, + Patch, + Delete, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { RewardService } from './reward.service'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { RewardResponseDto } from './dtos/reward-response.dto'; +import { CreateRewardDto } from './dtos/create-reward.dto'; +import { Role } from 'src/shared/enums/roles.enum'; +import { UpdateRewardDto } from './dtos/update-reward.dto'; +import { Public } from 'src/shared/decorators/public.decorator'; +import { Reward } from './reward.entity'; + +@Controller('reward') +@Injectable() +@ApiTags('Reward') +export class RewardController { + constructor(private readonly rewardService: RewardService) {} + + @Get() + @Public() + @ApiResponse({ + status: HttpStatus.OK, + type: Reward, + description: 'get all reward', + isArray: true, + }) + async findAll(): Promise { + const rewards = await this.rewardService.findAll(); + return rewards.map((reward) => new RewardResponseDto(reward)); + } + + @Get(':id') + @Public() + @ApiResponse({ + status: HttpStatus.OK, + type: Reward, + description: 'get reward', + }) + async findOne( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const reward = await this.rewardService.findOne({ where: { id } }); + return new RewardResponseDto(reward); + } + + @Post() + @HttpCode(HttpStatus.CREATED) + @Roles(Role.ADMIN) + @ApiBearerAuth() + @ApiResponse({ + status: HttpStatus.OK, + type: RewardResponseDto, + description: 'create reward', + }) + async create( + @Body() CreateRewardDto: CreateRewardDto, + ): Promise { + return this.rewardService.create(CreateRewardDto); + } + + @Patch(':id') + @Roles(Role.ADMIN) + @ApiBearerAuth() + @ApiResponse({ + status: HttpStatus.OK, + type: RewardResponseDto, + description: 'edit reward', + }) + async update( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @Body() UpdateRewardDto: UpdateRewardDto, + ): Promise { + const reward = await this.rewardService.update(id, UpdateRewardDto); + return new RewardResponseDto(reward); + } + + @Delete(':id') + @Roles(Role.ADMIN) + @ApiBearerAuth() + @ApiResponse({ + status: HttpStatus.OK, + description: 'delete reward', + }) + async delete( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise<{ message: string }> { + await this.rewardService.delete(id); + return { message: 'Reward delete successfully' }; + } +} diff --git a/src/reward/reward.entity.ts b/src/reward/reward.entity.ts index aa7bae0..f4a6929 100644 --- a/src/reward/reward.entity.ts +++ b/src/reward/reward.entity.ts @@ -5,6 +5,8 @@ import { PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; +import { Type } from './enums/type.enum'; +import { Status } from './enums/status.enum'; @Entity() export class Reward { @@ -22,21 +24,39 @@ export class Reward { @Column() thumbnail: string; - @Column() - type: string; + @Column({ + nullable: false, + type: 'enum', + enum: Type, + }) + type: Type; - @Column() + @Column({ + nullable: false, + }) points: number; - @Column() + @Column({ + nullable: false, + }) stock: number; - @Column() - status: string; + @Column({ + nullable: false, + type: 'enum', + enum: Status, + }) + status: Status; - @CreateDateColumn() + @CreateDateColumn({ + type: 'timestamp with time zone', + nullable: false, + }) createdAt: Date; - @UpdateDateColumn() - UpdatedAt: Date; + @UpdateDateColumn({ + type: 'timestamp with time zone', + nullable: false, + }) + updatedAt: Date; } diff --git a/src/reward/reward.module.ts b/src/reward/reward.module.ts index e69de29..dfb1af0 100644 --- a/src/reward/reward.module.ts +++ b/src/reward/reward.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { DatabaseModule } from 'src/database/database.module'; +import { RewardController } from './reward.controllers'; +import { rewardProviders } from './reward.providers'; +import { RewardService } from './reward.service'; + +@Module({ + imports: [DatabaseModule], + controllers: [RewardController], + providers: [...rewardProviders, RewardService], + exports: [RewardService], +}) +export class RewardModule {} diff --git a/src/reward/reward.providers.ts b/src/reward/reward.providers.ts new file mode 100644 index 0000000..b8a2e2c --- /dev/null +++ b/src/reward/reward.providers.ts @@ -0,0 +1,10 @@ +import { DataSource } from 'typeorm'; +import { Reward } from './reward.entity'; + +export const rewardProviders = [ + { + provide: 'RewardRepository', + useFactory: (DataSource: DataSource) => DataSource.getRepository(Reward), + inject: ['DataSource'], + }, +]; diff --git a/src/reward/reward.service.ts b/src/reward/reward.service.ts index e69de29..57af163 100644 --- a/src/reward/reward.service.ts +++ b/src/reward/reward.service.ts @@ -0,0 +1,57 @@ +import { + BadRequestException, + Inject, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { FindOneOptions, Repository } from 'typeorm'; +import { Reward } from './reward.entity'; +import { CreateRewardDto } from './dtos/create-reward.dto'; +import { UpdateRewardDto } from './dtos/update-reward.dto'; + +@Injectable() +export class RewardService { + constructor( + @Inject('RewardRepository') + private readonly rewardRepository: Repository, + ) {} + + async findAll(): Promise { + return this.rewardRepository.find(); + } + + async findOne(options: FindOneOptions): Promise { + const reward = await this.rewardRepository.findOne(options); + if (!reward) throw new NotFoundException('reward not found'); + return reward; + } + + async create(CreateRewardDto: CreateRewardDto): Promise { + try { + return this.rewardRepository.save(CreateRewardDto); + } catch (error) { + if (error instanceof Error) { + throw new BadRequestException(error.message); + } + } + } + + async update(id: string, UpdateRewardDto: UpdateRewardDto): Promise { + try { + await this.rewardRepository.update(id, UpdateRewardDto); + return this.rewardRepository.findOne({ where: { id } }); + } catch (error) { + if (error instanceof Error) + throw new NotFoundException('reward not found'); + } + } + + async delete(id: string): Promise { + try { + this.rewardRepository.delete(id); + } catch (error) { + if (error instanceof Error) + throw new NotFoundException('reward not found'); + } + } +} From 405d8634c85e73d814da3cc73525be226a5c73fa Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Thu, 14 Nov 2024 22:17:42 +0700 Subject: [PATCH 031/155] feat: implement course ownership functionality with new guard and decorator for course/module/chapter access control --- src/app.module.ts | 19 +++- src/chapter/chapter.controller.ts | 7 +- src/course-module/course-module.controller.ts | 10 +- src/course-module/course-module.service.ts | 2 +- src/course/course.controller.ts | 33 ++---- src/course/course.service.ts | 42 +------ .../decorators/course-ownership.decorator.ts | 27 +++++ src/shared/guards/course-ownership.guard.ts | 105 ++++++++++++++++++ 8 files changed, 172 insertions(+), 73 deletions(-) create mode 100644 src/shared/decorators/course-ownership.decorator.ts create mode 100644 src/shared/guards/course-ownership.guard.ts diff --git a/src/app.module.ts b/src/app.module.ts index e9fd6e7..586f42d 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -10,7 +10,6 @@ import { AuthModule } from './auth/auth.module'; import { CategoryModule } from './category/category.module'; import { ChapterModule } from './chapter/chapter.module'; import { CourseModuleModule } from './course-module/course-module.module'; -import { Course } from './course/course.entity'; import { CourseModule } from './course/course.module'; import { DatabaseModule } from './database/database.module'; import { databaseConfig } from './shared/configs/database.config'; @@ -21,8 +20,18 @@ import { UserStreak } from './user-streak/user-streak.entity'; import { UserStreakModule } from './user-streak/user-streak.module'; import { User } from './user/user.entity'; import { UserModule } from './user/user.module'; +import { CourseOwnershipGuard } from './shared/guards/course-ownership.guard'; +import { Course } from './course/course.entity'; +import { CourseModule as CourseModuleEntity } from './course-module/course-module.entity'; +import { Chapter } from './chapter/chapter.entity'; -const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); +const forFeatures = TypeOrmModule.forFeature([ + User, + UserStreak, + Course, + CourseModuleEntity, + Chapter +]); @Module({ imports: [ @@ -64,6 +73,10 @@ const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); provide: APP_GUARD, useClass: RolesGuard, }, + { + provide: APP_GUARD, + useClass: CourseOwnershipGuard, + } ], }) -export class AppModule {} +export class AppModule {} \ No newline at end of file diff --git a/src/chapter/chapter.controller.ts b/src/chapter/chapter.controller.ts index 4f6d0ae..799714c 100644 --- a/src/chapter/chapter.controller.ts +++ b/src/chapter/chapter.controller.ts @@ -30,6 +30,7 @@ import { } from './dtos/chapter-response.dto'; import { CreateChapterDto } from './dtos/create-chapter.dto'; import { UpdateChapterDto } from './dtos/update-chapter.dto'; +import { CourseOwnership } from 'src/shared/decorators/course-ownership.decorator'; @Controller('chapter') @ApiTags('Chapters') @@ -94,7 +95,7 @@ export class ChapterController { } @Patch(':id') - @Roles(Role.TEACHER) + @CourseOwnership({adminDraftOnly: true}) @ApiResponse({ status: HttpStatus.OK, type: ChapterResponseDto, @@ -113,7 +114,7 @@ export class ChapterController { } @Delete(':id') - @Roles(Role.ADMIN) + @CourseOwnership() @ApiResponse({ status: HttpStatus.OK, type: ChapterResponseDto, @@ -130,3 +131,5 @@ export class ChapterController { return this.chapterService.remove(id); } } + + diff --git a/src/course-module/course-module.controller.ts b/src/course-module/course-module.controller.ts index 2de8cc3..d34dfb4 100644 --- a/src/course-module/course-module.controller.ts +++ b/src/course-module/course-module.controller.ts @@ -31,13 +31,14 @@ import { } from './dtos/course-module-response.dto'; import { CreateCourseModuleDto } from './dtos/create-course-module.dto'; import { UpdateCourseModuleDto } from './dtos/update-course-module.dto'; +import { CourseOwnership } from 'src/shared/decorators/course-ownership.decorator'; @Controller('course-module') @ApiTags('Course Modules') @ApiBearerAuth() @Injectable() export class CourseModuleController { - constructor(private readonly courseModuleService: CourseModuleService) {} + constructor(private readonly courseModuleService: CourseModuleService) { } @Get() @ApiResponse({ @@ -65,7 +66,6 @@ export class CourseModuleController { description: 'Search by title', }) async findAll( - @Req() request: AuthenticatedRequest, @Query() query: PaginateQueryDto, ): Promise { return this.courseModuleService.findAll({ @@ -126,7 +126,9 @@ export class CourseModuleController { } @Patch(':id') - @Roles(Role.TEACHER) + @CourseOwnership({ + adminDraftOnly: true + }) @ApiParam({ name: 'id', type: String, @@ -140,7 +142,7 @@ export class CourseModuleController { } @Delete(':id') - @Roles(Role.TEACHER) + @CourseOwnership() @ApiParam({ name: 'id', type: String, diff --git a/src/course-module/course-module.service.ts b/src/course-module/course-module.service.ts index e7fbb1f..deca79c 100644 --- a/src/course-module/course-module.service.ts +++ b/src/course-module/course-module.service.ts @@ -188,4 +188,4 @@ export class CourseModuleService { return result; } -} +} \ No newline at end of file diff --git a/src/course/course.controller.ts b/src/course/course.controller.ts index 7a82bd1..ab7ff24 100644 --- a/src/course/course.controller.ts +++ b/src/course/course.controller.ts @@ -32,6 +32,7 @@ import { PaginatedCourseResponeDto, UpdateCourseDto, } from './dtos/index'; +import { CourseOwnership } from 'src/shared/decorators/course-ownership.decorator'; @Controller('course') @ApiTags('Course') @@ -41,7 +42,7 @@ export class CourseController { constructor( private readonly courseService: CourseService, private readonly categoryService: CategoryService, - ) {} + ) { } @Get() @ApiResponse({ status: HttpStatus.OK, @@ -137,7 +138,9 @@ export class CourseController { } @Patch(':id') - @Roles(Role.TEACHER, Role.ADMIN) + @CourseOwnership({ + adminDraftOnly: true + }) @ApiParam({ name: 'id', type: String, @@ -151,14 +154,7 @@ export class CourseController { async update( @Req() request: AuthenticatedRequest, @Body() updateCourseDto: UpdateCourseDto, - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, + @Param('id', ParseUUIDPipe) id: string, ): Promise { const category = await this.categoryService.findOne({ where: { id: updateCourseDto.categoryId }, @@ -170,16 +166,13 @@ export class CourseController { const course = await this.courseService.update( id, - request.user.id, - request.user.role, updateCourseDto, ); return new CourseResponseDto(course); } - @Delete(':id') - @Roles(Role.TEACHER, Role.ADMIN) + @CourseOwnership() @ApiParam({ name: 'id', type: String, @@ -190,16 +183,8 @@ export class CourseController { description: 'Delete course by id', }) async delete( - @Req() request: AuthenticatedRequest, - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, + @Param('id', ParseUUIDPipe) id: string, ): Promise { - await this.courseService.delete(id, request.user.id, request.user.role); + await this.courseService.delete(id); } } diff --git a/src/course/course.service.ts b/src/course/course.service.ts index b132a36..8a1ee95 100644 --- a/src/course/course.service.ts +++ b/src/course/course.service.ts @@ -20,7 +20,7 @@ export class CourseService { constructor( @Inject('CourseRepository') private readonly courseRepository: Repository, - ) {} + ) { } async findAll({ page = 1, @@ -97,8 +97,6 @@ export class CourseService { } async update( id: string, - userId: string, - role: Role, updateCourseDto: UpdateCourseDto, ): Promise { const existingCourse = await this.courseRepository.findOne({ @@ -113,7 +111,6 @@ export class CourseService { throw new NotFoundException('Course not found'); } - await this.validateUpdatePermissions(existingCourse, userId, role); this.validateStatusTransition( existingCourse.status, updateCourseDto.status, @@ -128,22 +125,14 @@ export class CourseService { return updatedCourse; } - async delete(id: string, userId: string, role: Role): Promise { - if (role === Role.TEACHER) await this.checkOwnership(id, userId); + async delete(id: string): Promise { try { await this.courseRepository.delete(id); } catch (error) { - if (error instanceof Error) - throw new NotFoundException('Course not found'); + throw new NotFoundException('Course not found'); } } - private async checkOwnership(id: string, userId: string): Promise { - const course = await this.courseRepository.findOne({ where: { id } }); - if (course.teacher.id !== userId) - throw new BadRequestException("You don't own this course"); - } - private buildWhereCondition( userId: string, role: Role, @@ -179,31 +168,6 @@ export class CourseService { return buildCondition(); } - private async validateUpdatePermissions( - course: Course, - userId: string, - role: Role, - ): Promise { - switch (role) { - case Role.TEACHER: - if (course.teacher.id !== userId) { - throw new ForbiddenException('You can only update your own courses'); - } - break; - - case Role.ADMIN: - if (course.status !== CourseStatus.DRAFT) { - throw new BadRequestException( - 'Admin can only update courses in draft status', - ); - } - break; - - default: - throw new BadRequestException('Invalid role'); - } - } - private validateStatusTransition( currentStatus: CourseStatus, newStatus?: CourseStatus, diff --git a/src/shared/decorators/course-ownership.decorator.ts b/src/shared/decorators/course-ownership.decorator.ts new file mode 100644 index 0000000..cb50a4f --- /dev/null +++ b/src/shared/decorators/course-ownership.decorator.ts @@ -0,0 +1,27 @@ +import { SetMetadata } from '@nestjs/common'; + +export const COURSE_OWNERSHIP_KEY = 'courseOwnership' as const; +export const ADMIN_DRAFT_ONLY_KEY = 'adminDraftOnly' as const; + +interface CourseAccessConfig { + adminDraftOnly?: boolean; +} + +type DecoratorFunction = ( + target: object | Function, + propertyKey?: string | symbol, + descriptor?: PropertyDescriptor +) => any; + +export const CourseOwnership = (config: CourseAccessConfig = {}): DecoratorFunction => { + return ( + target: object | Function, + propertyKey?: string | symbol, + descriptor?: PropertyDescriptor + ) => { + if (config.adminDraftOnly) { + SetMetadata(ADMIN_DRAFT_ONLY_KEY, true)(target, propertyKey, descriptor); + } + return SetMetadata(COURSE_OWNERSHIP_KEY, true)(target, propertyKey, descriptor); + }; +}; \ No newline at end of file diff --git a/src/shared/guards/course-ownership.guard.ts b/src/shared/guards/course-ownership.guard.ts new file mode 100644 index 0000000..de8a776 --- /dev/null +++ b/src/shared/guards/course-ownership.guard.ts @@ -0,0 +1,105 @@ +import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Chapter } from 'src/chapter/chapter.entity'; +import { CourseModule } from 'src/course-module/course-module.entity'; +import { Course } from 'src/course/course.entity'; +import { Repository } from 'typeorm'; +import { CourseStatus, Role } from '../enums'; +import { ADMIN_DRAFT_ONLY_KEY, COURSE_OWNERSHIP_KEY } from '../decorators/course-ownership.decorator'; + + +type ResourceType = 'course' | 'module' | 'chapter'; + +@Injectable() +export class CourseOwnershipGuard implements CanActivate { + private readonly pathToType: Record = { + '/course/': 'course', + '/course-module/': 'module', + '/chapter/': 'chapter' + }; + + constructor( + private reflector: Reflector, + @InjectRepository(Course) + private courseRepo: Repository, + @InjectRepository(CourseModule) + private moduleRepo: Repository, + @InjectRepository(Chapter) + private chapterRepo: Repository + ) {} + + async canActivate(context: ExecutionContext): Promise { + if (!this.isOwnershipRequired(context)) return true; + + const { user, params, path } = this.getRequestData(context); + const course = await this.getCourse(path, params.id); + + if (!course) throw new UnauthorizedException('Course not found'); + + if (user.role === Role.ADMIN) { + return this.validateAdminAccess(context, course); + } + + if (user.role === Role.TEACHER) { + return this.validateTeacherAccess(user.id, course); + } + + throw new UnauthorizedException('Insufficient permissions'); + } + + private isOwnershipRequired(context: ExecutionContext): boolean { + return this.reflector.getAllAndOverride(COURSE_OWNERSHIP_KEY, [ + context.getHandler(), + context.getClass() + ]); + } + + private getRequestData(context: ExecutionContext) { + const request = context.switchToHttp().getRequest(); + return { user: request.user, params: request.params, path: request.path }; + } + + private async getCourse(path: string, id: string): Promise { + const type = Object.entries(this.pathToType) + .find(([key]) => path.includes(key))?.[1]; + + if (!type) return null; + + const queries = { + course: () => this.courseRepo.findOne({ + where: { id }, + relations: { teacher: true } + }), + module: () => this.moduleRepo.findOne({ + where: { id }, + relations: { course: { teacher: true } } + }).then(m => m?.course), + chapter: () => this.chapterRepo.findOne({ + where: { id }, + relations: { module: { course: { teacher: true } } } + }).then(c => c?.module?.course) + }; + + return await queries[type]().catch(() => null); + } + + private validateAdminAccess(context: ExecutionContext, course: Course): boolean { + const adminDraftOnly = this.reflector.getAllAndOverride(ADMIN_DRAFT_ONLY_KEY, [ + context.getHandler(), + context.getClass() + ]); + + if (adminDraftOnly && course.status !== CourseStatus.DRAFT) { + throw new UnauthorizedException('Admin can only access draft courses'); + } + return true; + } + + private validateTeacherAccess(userId: string, course: Course): boolean { + if (course.teacher.id !== userId) { + throw new UnauthorizedException('You can only access your own courses'); + } + return true; + } +} \ No newline at end of file From f7e3cc23bc8848d859324a6ea0584234856b6a6b Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Fri, 15 Nov 2024 12:09:21 +0700 Subject: [PATCH 032/155] feat: enhance chapter and course module services with error handling for course ownership and order index validation --- src/chapter/chapter.controller.ts | 19 ++++++- src/chapter/chapter.module.ts | 3 +- src/chapter/chapter.service.ts | 49 ++++++++++------ src/chapter/dtos/create-chapter.dto.ts | 12 +--- src/chapter/dtos/update-chapter.dto.ts | 14 ++++- src/course-module/course-module.controller.ts | 19 ++++++- src/course-module/course-module.service.ts | 56 ++++++++++--------- .../dtos/create-course-module.dto.ts | 10 +--- .../dtos/update-course-module.dto.ts | 15 ++++- 9 files changed, 130 insertions(+), 67 deletions(-) diff --git a/src/chapter/chapter.controller.ts b/src/chapter/chapter.controller.ts index 799714c..b37dcea 100644 --- a/src/chapter/chapter.controller.ts +++ b/src/chapter/chapter.controller.ts @@ -1,4 +1,5 @@ import { + BadRequestException, Body, Controller, Delete, @@ -31,13 +32,14 @@ import { import { CreateChapterDto } from './dtos/create-chapter.dto'; import { UpdateChapterDto } from './dtos/update-chapter.dto'; import { CourseOwnership } from 'src/shared/decorators/course-ownership.decorator'; +import { CourseModuleService } from 'src/course-module/course-module.service'; @Controller('chapter') @ApiTags('Chapters') @ApiBearerAuth() @Injectable() export class ChapterController { - constructor(private readonly chapterService: ChapterService) {} + constructor(private readonly chapterService: ChapterService, private readonly courseModuleService: CourseModuleService) { } @Get() @ApiResponse({ @@ -76,8 +78,15 @@ export class ChapterController { description: 'Chapter ID', }) async findOne( + @Req() request: AuthenticatedRequest, @Param('id', ParseUUIDPipe) id: string, ): Promise { + const courseModule = await this.courseModuleService.findOne(id, { where: { id } }); + + if (!courseModule && courseModule.course.teacher.id !== request.user.id) { + throw new BadRequestException('Course Module not found'); + } + return this.chapterService.findOne(id, { where: { id } }); } @@ -107,9 +116,17 @@ export class ChapterController { description: 'Chapter ID', }) async update( + @Req() request: AuthenticatedRequest, @Param('id', ParseUUIDPipe) id: string, @Body() updateChapterDto: UpdateChapterDto, ): Promise { + if (updateChapterDto.moduleId != null) { + const courseModule = await this.courseModuleService.findOne(id, { where: { id } }); + + if (!courseModule && courseModule.course.teacher.id !== request.user.id) { + throw new BadRequestException('Course Module not found'); + } + } return this.chapterService.update(id, updateChapterDto); } diff --git a/src/chapter/chapter.module.ts b/src/chapter/chapter.module.ts index bfdb840..6585cf3 100644 --- a/src/chapter/chapter.module.ts +++ b/src/chapter/chapter.module.ts @@ -4,9 +4,10 @@ import { ChapterController } from './chapter.controller'; import { Chapter } from './chapter.entity'; import { chapterProviders } from './chapter.provider'; import { ChapterService } from './chapter.service'; +import { CourseModuleModule } from 'src/course-module/course-module.module'; @Module({ - imports: [DatabaseModule], + imports: [DatabaseModule, CourseModuleModule], controllers: [ChapterController], providers: [...chapterProviders, ChapterService], exports: [ChapterService], diff --git a/src/chapter/chapter.service.ts b/src/chapter/chapter.service.ts index 9e06bd4..7550ee4 100644 --- a/src/chapter/chapter.service.ts +++ b/src/chapter/chapter.service.ts @@ -16,7 +16,7 @@ export class ChapterService { constructor( @InjectRepository(Chapter) private readonly chapterRepository: Repository, - ) {} + ) { } async findAll({ page = 1, @@ -84,21 +84,13 @@ export class ChapterService { } async create(createChapterDto: CreateChapterDto): Promise { - if (!createChapterDto.orderIndex) { - createChapterDto.orderIndex = await this.validateAndGetNextOrderIndex( - createChapterDto.moduleId, - ); - } else { - const existingChapter = await this.chapterRepository.findOne({ - where: { orderIndex: createChapterDto.orderIndex }, - }); - if (existingChapter) { - throw new BadRequestException('Order index is duplicated'); - } - } + let orderIndex = await this.validateAndGetNextOrderIndex( + createChapterDto.moduleId, + ); + - const chapter = this.chapterRepository.create(createChapterDto); + const chapter = this.chapterRepository.create({...createChapterDto, orderIndex: orderIndex}); await this.chapterRepository.save(chapter); return chapter; @@ -126,7 +118,9 @@ export class ChapterService { if (!chapter) { throw new NotFoundException('Chapter not found'); } - + if (updateChapterDto.orderIndex != null) { + await this.validateOrderIndex(chapter.moduleId, updateChapterDto.orderIndex); + } if ( updateChapterDto.orderIndex && updateChapterDto.orderIndex !== chapter.orderIndex @@ -136,7 +130,7 @@ export class ChapterService { }); if (existingChapter) { - throw new BadRequestException('Order index is duplicated'); + await this.chapterRepository.update(existingChapter.id, { orderIndex: chapter.orderIndex }); } } @@ -159,4 +153,27 @@ export class ChapterService { return result; } + + private async validateOrderIndex(moduleId: string, orderIndex: number): Promise { + const existingModules = await this.chapterRepository.find({ + where: { moduleId }, + order: { orderIndex: 'ASC' }, + }); + if (existingModules.length === 0) { + if (orderIndex !== 1) { + throw new BadRequestException( + 'Order index should be 1 when there are no modules in the course' + ); + } + return; + } + const minIndex = 1; + const maxIndex = existingModules[existingModules.length - 1].orderIndex; + + if (orderIndex < minIndex || orderIndex > maxIndex) { + throw new BadRequestException( + `Order index must be between ${minIndex} and ${maxIndex}` + ); + } + } } diff --git a/src/chapter/dtos/create-chapter.dto.ts b/src/chapter/dtos/create-chapter.dto.ts index 2a89f06..06fca66 100644 --- a/src/chapter/dtos/create-chapter.dto.ts +++ b/src/chapter/dtos/create-chapter.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsBoolean, IsNotEmpty, IsNumber, IsString } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsNumber, IsString, IsUUID } from 'class-validator'; export class CreateChapterDto { @IsNotEmpty() @@ -56,17 +56,9 @@ export class CreateChapterDto { }) duration: number; - @IsNotEmpty() - @IsNumber() - @ApiProperty({ - description: 'Chapter order index', - type: Number, - example: 1, - }) - orderIndex: number; @IsNotEmpty() - @IsString() + @IsUUID(4) @ApiProperty({ description: 'Module ID', type: String, diff --git a/src/chapter/dtos/update-chapter.dto.ts b/src/chapter/dtos/update-chapter.dto.ts index 7a4eaa6..78be79a 100644 --- a/src/chapter/dtos/update-chapter.dto.ts +++ b/src/chapter/dtos/update-chapter.dto.ts @@ -1,4 +1,16 @@ import { PartialType } from '@nestjs/mapped-types'; import { CreateChapterDto } from './create-chapter.dto'; +import { IsNumber, IsOptional } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; -export class UpdateChapterDto extends PartialType(CreateChapterDto) {} +export class UpdateChapterDto extends PartialType(CreateChapterDto) { + + @IsOptional() + @IsNumber() + @ApiProperty({ + description: 'Chapter order index', + type: Number, + example: 1, + }) + orderIndex: number; +} diff --git a/src/course-module/course-module.controller.ts b/src/course-module/course-module.controller.ts index d34dfb4..ed63ff3 100644 --- a/src/course-module/course-module.controller.ts +++ b/src/course-module/course-module.controller.ts @@ -32,13 +32,15 @@ import { import { CreateCourseModuleDto } from './dtos/create-course-module.dto'; import { UpdateCourseModuleDto } from './dtos/update-course-module.dto'; import { CourseOwnership } from 'src/shared/decorators/course-ownership.decorator'; +import { Course } from 'src/course/course.entity'; +import { CourseService } from 'src/course/course.service'; @Controller('course-module') @ApiTags('Course Modules') @ApiBearerAuth() @Injectable() export class CourseModuleController { - constructor(private readonly courseModuleService: CourseModuleService) { } + constructor(private readonly courseModuleService: CourseModuleService, private readonly courseService: CourseService) { } @Get() @ApiResponse({ @@ -113,15 +115,21 @@ export class CourseModuleController { } @Post() - @Roles(Role.TEACHER) @ApiResponse({ status: HttpStatus.CREATED, type: CourseModuleResponseDto, description: 'Create a course module', }) async create( + @Req() request: AuthenticatedRequest, @Body() createCourseModuleDto: CreateCourseModuleDto, ): Promise { + const course = await this.courseService.findOne(request.user.id, Role.TEACHER, { where: { id: createCourseModuleDto.courseId } }); + + if (!course) { + throw new BadRequestException('Course not found'); + } + return this.courseModuleService.create(createCourseModuleDto); } @@ -135,9 +143,16 @@ export class CourseModuleController { description: 'Course Module ID', }) async update( + @Req() request: AuthenticatedRequest, @Param('id', new ParseUUIDPipe()) id: string, @Body() updateCourseModuleDto: UpdateCourseModuleDto, ): Promise { + if (updateCourseModuleDto.courseId != null) { + const course = await this.courseService.findOne(request.user.id, request.user.role, { where: { id: updateCourseModuleDto.courseId } }); + if (!course) { + throw new BadRequestException('Course not found'); + } + } return this.courseModuleService.update(id, updateCourseModuleDto); } diff --git a/src/course-module/course-module.service.ts b/src/course-module/course-module.service.ts index deca79c..5d613c5 100644 --- a/src/course-module/course-module.service.ts +++ b/src/course-module/course-module.service.ts @@ -16,7 +16,7 @@ export class CourseModuleService { constructor( @Inject('CourseModuleRepository') private readonly courseModuleRepository: Repository, - ) {} + ) { } async findAll({ page = 1, @@ -98,27 +98,8 @@ export class CourseModuleService { async create( createCourseModuleDto: CreateCourseModuleDto, ): Promise { - if (!createCourseModuleDto.orderIndex) { - createCourseModuleDto.orderIndex = - await this.validateAndGetNextOrderIndex(createCourseModuleDto.courseId); - } else { - const existingModule = await this.courseModuleRepository.findOne({ - where: { - courseId: createCourseModuleDto.courseId, - orderIndex: createCourseModuleDto.orderIndex, - }, - }); - - if (existingModule) { - throw new BadRequestException( - `Module with orderIndex ${createCourseModuleDto.orderIndex} already exists in this course`, - ); - } - } - - const courseModule = this.courseModuleRepository.create( - createCourseModuleDto, - ); + let orderIndex = await this.validateAndGetNextOrderIndex(createCourseModuleDto.courseId); + const courseModule = this.courseModuleRepository.create({ ...createCourseModuleDto, orderIndex: orderIndex }); await this.courseModuleRepository.save(courseModule); return courseModule; @@ -148,7 +129,9 @@ export class CourseModuleService { if (!courseModule) { throw new BadRequestException('Course module not found'); } - + if (updateCourseModuleDto.orderIndex != null) { + await this.validateOrderIndex(courseModule.courseId, updateCourseModuleDto.orderIndex); + } if ( updateCourseModuleDto.orderIndex && updateCourseModuleDto.orderIndex !== courseModule.orderIndex @@ -161,9 +144,7 @@ export class CourseModuleService { }); if (existingModule) { - throw new BadRequestException( - `Module with orderIndex ${updateCourseModuleDto.orderIndex} already exists in this course`, - ); + await this.courseModuleRepository.update(existingModule.id, { orderIndex: courseModule.orderIndex }); } } @@ -188,4 +169,27 @@ export class CourseModuleService { return result; } + private async validateOrderIndex(courseId: string, orderIndex: number): Promise { + const existingModules = await this.courseModuleRepository.find({ + where: { courseId }, + order: { orderIndex: 'ASC' }, + }); + if (existingModules.length === 0) { + if (orderIndex !== 1) { + throw new BadRequestException( + 'Order index should be 1 when there are no modules in the course' + ); + } + return; + } + console.log(orderIndex) + const minIndex = 1; + const maxIndex = existingModules[existingModules.length - 1].orderIndex; + + if (orderIndex < minIndex || orderIndex > maxIndex) { + throw new BadRequestException( + `Order index is invalid` + ); + } + } } \ No newline at end of file diff --git a/src/course-module/dtos/create-course-module.dto.ts b/src/course-module/dtos/create-course-module.dto.ts index 36407b8..44242ff 100644 --- a/src/course-module/dtos/create-course-module.dto.ts +++ b/src/course-module/dtos/create-course-module.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsNotEmpty } from 'class-validator'; +import { IsNotEmpty, IsUUID } from 'class-validator'; export class CreateCourseModuleDto { @IsNotEmpty() @@ -18,15 +18,9 @@ export class CreateCourseModuleDto { }) description: string; - @IsNotEmpty() - @ApiProperty({ - description: 'Course Module order index', - type: Number, - example: 1, - }) - orderIndex: number; @IsNotEmpty() + @IsUUID(4) @ApiProperty({ description: 'Course ID', type: String, diff --git a/src/course-module/dtos/update-course-module.dto.ts b/src/course-module/dtos/update-course-module.dto.ts index 33898ed..c01c043 100644 --- a/src/course-module/dtos/update-course-module.dto.ts +++ b/src/course-module/dtos/update-course-module.dto.ts @@ -1,4 +1,15 @@ -import { PartialType } from '@nestjs/swagger'; +import { ApiProperty, PartialType } from '@nestjs/swagger'; import { CreateCourseModuleDto } from './create-course-module.dto'; +import { IsNumber, IsOptional } from 'class-validator'; -export class UpdateCourseModuleDto extends PartialType(CreateCourseModuleDto) {} +export class UpdateCourseModuleDto extends PartialType(CreateCourseModuleDto) { + @IsOptional() + @IsNumber() + @ApiProperty({ + description: 'Course Module order index', + type: Number, + example: 1, + }) + orderIndex?: number; + +} From 01dc42d2ba8196aa8d2833639ac457c03a394f6f Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Fri, 15 Nov 2024 12:11:23 +0700 Subject: [PATCH 033/155] refactor: remove debugging log in CourseModuleService to clean up console output --- src/course-module/course-module.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/course-module/course-module.service.ts b/src/course-module/course-module.service.ts index 5d613c5..8b5db12 100644 --- a/src/course-module/course-module.service.ts +++ b/src/course-module/course-module.service.ts @@ -182,7 +182,6 @@ export class CourseModuleService { } return; } - console.log(orderIndex) const minIndex = 1; const maxIndex = existingModules[existingModules.length - 1].orderIndex; From cff0cf075cdc87e63f8a6682c9823ea2ff34b0cd Mon Sep 17 00:00:00 2001 From: ganthepro Date: Fri, 15 Nov 2024 13:46:57 +0700 Subject: [PATCH 034/155] feat: add file module with S3 integration and folder enum --- .env.example | 6 +- package.json | 3 + pnpm-lock.yaml | 1238 +++++++++++++++++ src/app.module.ts | 85 +- src/file/enums/folder.enum.ts | 6 + src/file/file.module.ts | 8 + src/file/file.service.ts | 66 + src/shared/configs/dotenv.config.ts | 4 + .../constants/global-config.constant.ts | 4 + 9 files changed, 1377 insertions(+), 43 deletions(-) create mode 100644 src/file/enums/folder.enum.ts create mode 100644 src/file/file.module.ts create mode 100644 src/file/file.service.ts diff --git a/.env.example b/.env.example index f4e5643..4875d03 100644 --- a/.env.example +++ b/.env.example @@ -9,4 +9,8 @@ CORS_ALLOW_ORIGIN=http://localhost:5173 JWT_ACCESS_SECRET=+W5LRxRFUk8MKSHMeRYevRg JWT_REFRESH_SECRET=2Z7bB8GizN15kaHzB+H8Tg JWT_ACCESS_EXPIRATION=1d -JWT_REFRESH_EXPIRATION=7d \ No newline at end of file +JWT_REFRESH_EXPIRATION=7d +AWS_ACCESS_KEY_ID=youraccesskey +AWS_SECRET_ACCESS_KEY=yoursecretkey +AWS_REGION=yourregion +AWS_BUCKET_NAME=yourbucketname \ No newline at end of file diff --git a/package.json b/package.json index a53a3f5..ecd4a51 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { + "@aws-sdk/client-s3": "^3.693.0", "@nestjs/class-validator": "0.13.1", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.3.0", @@ -34,6 +35,7 @@ "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^8.0.5", "@nestjs/typeorm": "^10.0.2", + "@smithy/types": "^3.7.1", "argon2": "^0.41.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", @@ -52,6 +54,7 @@ "@nestjs/testing": "^10.0.0", "@types/express": "^5.0.0", "@types/jest": "^29.5.2", + "@types/multer": "^1.4.12", "@types/node": "^20.3.1", "@types/supertest": "^6.0.0", "@typescript-eslint/eslint-plugin": "^8.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dac0a47..f2a5581 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@aws-sdk/client-s3': + specifier: ^3.693.0 + version: 3.693.0 '@nestjs/class-validator': specifier: 0.13.1 version: 0.13.1 @@ -35,6 +38,9 @@ importers: '@nestjs/typeorm': specifier: ^10.0.2 version: 10.0.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3))) + '@smithy/types': + specifier: ^3.7.1 + version: 3.7.1 argon2: specifier: ^0.41.1 version: 0.41.1 @@ -84,6 +90,9 @@ importers: '@types/jest': specifier: ^29.5.2 version: 29.5.14 + '@types/multer': + specifier: ^1.4.12 + version: 1.4.12 '@types/node': specifier: ^20.3.1 version: 20.17.6 @@ -157,6 +166,169 @@ packages: resolution: {integrity: sha512-I5wviiIqiFwar9Pdk30Lujk8FczEEc18i22A5c6Z9lbmhPQdTroDnEQdsfXjy404wPe8H62s0I15o4pmMGfTYQ==} engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@aws-crypto/crc32@5.2.0': + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/crc32c@5.2.0': + resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} + + '@aws-crypto/sha1-browser@5.2.0': + resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} + + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/client-s3@3.693.0': + resolution: {integrity: sha512-vgGI2e0Q6pzyhqfrSysi+sk/i+Nl+lMon67oqj/57RcCw9daL1/inpS+ADuwHpiPWkrg+U0bOXnmHjkLeTslJg==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/client-sso-oidc@3.693.0': + resolution: {integrity: sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.693.0 + + '@aws-sdk/client-sso@3.693.0': + resolution: {integrity: sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/client-sts@3.693.0': + resolution: {integrity: sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/core@3.693.0': + resolution: {integrity: sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/credential-provider-env@3.693.0': + resolution: {integrity: sha512-hMUZaRSF7+iBKZfBHNLihFs9zvpM1CB8MBOTnTp5NGCVkRYF3SB2LH+Kcippe0ats4qCyB1eEoyQX99rERp2iQ==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/credential-provider-http@3.693.0': + resolution: {integrity: sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/credential-provider-ini@3.693.0': + resolution: {integrity: sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.693.0 + + '@aws-sdk/credential-provider-node@3.693.0': + resolution: {integrity: sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/credential-provider-process@3.693.0': + resolution: {integrity: sha512-cvxQkrTWHHjeHrPlj7EWXPnFSq8x7vMx+Zn1oTsMpCY445N9KuzjfJTkmNGwU2GT6rSZI9/0MM02aQvl5bBBTQ==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/credential-provider-sso@3.693.0': + resolution: {integrity: sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.693.0': + resolution: {integrity: sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.693.0 + + '@aws-sdk/middleware-bucket-endpoint@3.693.0': + resolution: {integrity: sha512-cPIa+lxMYiFRHtxKfNIVSFGO6LSgZCk42pu3d7KGwD6hu6vXRD5B2/DD3rPcEH1zgl2j0Kx1oGAV7SRXKHSFag==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-expect-continue@3.693.0': + resolution: {integrity: sha512-MuK/gsJWpHz6Tv0CqTCS+QNOxLa2RfPh1biVCu/uO3l7kA0TjQ/C+tfgKvLXeH103tuDrOVINK+bt2ENmI3SWg==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-flexible-checksums@3.693.0': + resolution: {integrity: sha512-xkS6zjuE11ob93H9t65kHzphXcUMnN2SmIm2wycUPg+hi8Q6DJA6U2p//6oXkrr9oHy1QvwtllRd7SAd63sFKQ==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-host-header@3.693.0': + resolution: {integrity: sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-location-constraint@3.693.0': + resolution: {integrity: sha512-eDAExTZ9uNIP7vs2JCVCOuWJauGueisBSn+Ovt7UvvuEUp6KOIJqn8oFxWmyUQu2GvbG4OcaTLgbqD95YHTB0Q==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-logger@3.693.0': + resolution: {integrity: sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-recursion-detection@3.693.0': + resolution: {integrity: sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-sdk-s3@3.693.0': + resolution: {integrity: sha512-5A++RBjJ3guyq5pbYs+Oq5hMlA8CK2OWaHx09cxVfhHWl/RoaY8DXrft4gnhoUEBrrubyMw7r9j7RIMLvS58kg==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-ssec@3.693.0': + resolution: {integrity: sha512-Ro5vzI7SRgEeuoMk3fKqFjGv6mG4c7VsSCDwnkiasmafQFBTPvUIpgmu2FXMHqW/OthvoiOzpSrlJ9Bwlx2f8A==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/middleware-user-agent@3.693.0': + resolution: {integrity: sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/region-config-resolver@3.693.0': + resolution: {integrity: sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/signature-v4-multi-region@3.693.0': + resolution: {integrity: sha512-s7zbbsoVIriTR4ZGaateKuTqz6ddpazAyHvjk7I9kd+NvGNPiuAI18UdbuiiRI6K5HuYKf1ah6mKWFGPG15/kQ==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/token-providers@3.693.0': + resolution: {integrity: sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sso-oidc': ^3.693.0 + + '@aws-sdk/types@3.692.0': + resolution: {integrity: sha512-RpNvzD7zMEhiKgmlxGzyXaEcg2khvM7wd5sSHVapOcrde1awQSOMGI4zKBQ+wy5TnDfrm170ROz/ERLYtrjPZA==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/util-arn-parser@3.693.0': + resolution: {integrity: sha512-WC8x6ca+NRrtpAH64rWu+ryDZI3HuLwlEr8EU6/dbC/pt+r/zC0PBoC15VEygUaBA+isppCikQpGyEDu0Yj7gQ==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/util-endpoints@3.693.0': + resolution: {integrity: sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/util-locate-window@3.693.0': + resolution: {integrity: sha512-ttrag6haJLWABhLqtg1Uf+4LgHWIMOVSYL+VYZmAp2v4PUGOwWmWQH0Zk8RM7YuQcLfH/EoR72/Yxz6A4FKcuw==} + engines: {node: '>=16.0.0'} + + '@aws-sdk/util-user-agent-browser@3.693.0': + resolution: {integrity: sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==} + + '@aws-sdk/util-user-agent-node@3.693.0': + resolution: {integrity: sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==} + engines: {node: '>=16.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/xml-builder@3.693.0': + resolution: {integrity: sha512-C/rPwJcqnV8VDr2/VtcQnymSpcfEEgH1Jm6V0VmfXNZFv4Qzf1eCS8nsec0gipYgZB+cBBjfXw5dAk6pJ8ubpw==} + engines: {node: '>=16.0.0'} + '@babel/code-frame@7.26.2': resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} @@ -650,6 +822,209 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@smithy/abort-controller@3.1.8': + resolution: {integrity: sha512-+3DOBcUn5/rVjlxGvUPKc416SExarAQ+Qe0bqk30YSUjbepwpS7QN0cyKUSifvLJhdMZ0WPzPP5ymut0oonrpQ==} + engines: {node: '>=16.0.0'} + + '@smithy/chunked-blob-reader-native@3.0.1': + resolution: {integrity: sha512-VEYtPvh5rs/xlyqpm5NRnfYLZn+q0SRPELbvBV+C/G7IQ+ouTuo+NKKa3ShG5OaFR8NYVMXls9hPYLTvIKKDrQ==} + + '@smithy/chunked-blob-reader@4.0.0': + resolution: {integrity: sha512-jSqRnZvkT4egkq/7b6/QRCNXmmYVcHwnJldqJ3IhVpQE2atObVJ137xmGeuGFhjFUr8gCEVAOKwSY79OvpbDaQ==} + + '@smithy/config-resolver@3.0.12': + resolution: {integrity: sha512-YAJP9UJFZRZ8N+UruTeq78zkdjUHmzsY62J4qKWZ4SXB4QXJ/+680EfXXgkYA2xj77ooMqtUY9m406zGNqwivQ==} + engines: {node: '>=16.0.0'} + + '@smithy/core@2.5.3': + resolution: {integrity: sha512-96uW8maifUSmehaeW7uydWn7wBc98NEeNI3zN8vqakGpyCQgzyJaA64Z4FCOUmAdCJkhppd/7SZ798Fo4Xx37g==} + engines: {node: '>=16.0.0'} + + '@smithy/credential-provider-imds@3.2.7': + resolution: {integrity: sha512-cEfbau+rrWF8ylkmmVAObOmjbTIzKyUC5TkBL58SbLywD0RCBC4JAUKbmtSm2w5KUJNRPGgpGFMvE2FKnuNlWQ==} + engines: {node: '>=16.0.0'} + + '@smithy/eventstream-codec@3.1.9': + resolution: {integrity: sha512-F574nX0hhlNOjBnP+noLtsPFqXnWh2L0+nZKCwcu7P7J8k+k+rdIDs+RMnrMwrzhUE4mwMgyN0cYnEn0G8yrnQ==} + + '@smithy/eventstream-serde-browser@3.0.13': + resolution: {integrity: sha512-Nee9m+97o9Qj6/XeLz2g2vANS2SZgAxV4rDBMKGHvFJHU/xz88x2RwCkwsvEwYjSX4BV1NG1JXmxEaDUzZTAtw==} + engines: {node: '>=16.0.0'} + + '@smithy/eventstream-serde-config-resolver@3.0.10': + resolution: {integrity: sha512-K1M0x7P7qbBUKB0UWIL5KOcyi6zqV5mPJoL0/o01HPJr0CSq3A9FYuJC6e11EX6hR8QTIR++DBiGrYveOu6trw==} + engines: {node: '>=16.0.0'} + + '@smithy/eventstream-serde-node@3.0.12': + resolution: {integrity: sha512-kiZymxXvZ4tnuYsPSMUHe+MMfc4FTeFWJIc0Q5wygJoUQM4rVHNghvd48y7ppuulNMbuYt95ah71pYc2+o4JOA==} + engines: {node: '>=16.0.0'} + + '@smithy/eventstream-serde-universal@3.0.12': + resolution: {integrity: sha512-1i8ifhLJrOZ+pEifTlF0EfZzMLUGQggYQ6WmZ4d5g77zEKf7oZ0kvh1yKWHPjofvOwqrkwRDVuxuYC8wVd662A==} + engines: {node: '>=16.0.0'} + + '@smithy/fetch-http-handler@4.1.1': + resolution: {integrity: sha512-bH7QW0+JdX0bPBadXt8GwMof/jz0H28I84hU1Uet9ISpzUqXqRQ3fEZJ+ANPOhzSEczYvANNl3uDQDYArSFDtA==} + + '@smithy/hash-blob-browser@3.1.9': + resolution: {integrity: sha512-wOu78omaUuW5DE+PVWXiRKWRZLecARyP3xcq5SmkXUw9+utgN8HnSnBfrjL2B/4ZxgqPjaAJQkC/+JHf1ITVaQ==} + + '@smithy/hash-node@3.0.10': + resolution: {integrity: sha512-3zWGWCHI+FlJ5WJwx73Mw2llYR8aflVyZN5JhoqLxbdPZi6UyKSdCeXAWJw9ja22m6S6Tzz1KZ+kAaSwvydi0g==} + engines: {node: '>=16.0.0'} + + '@smithy/hash-stream-node@3.1.9': + resolution: {integrity: sha512-3XfHBjSP3oDWxLmlxnt+F+FqXpL3WlXs+XXaB6bV9Wo8BBu87fK1dSEsyH7Z4ZHRmwZ4g9lFMdf08m9hoX1iRA==} + engines: {node: '>=16.0.0'} + + '@smithy/invalid-dependency@3.0.10': + resolution: {integrity: sha512-Lp2L65vFi+cj0vFMu2obpPW69DU+6O5g3086lmI4XcnRCG8PxvpWC7XyaVwJCxsZFzueHjXnrOH/E0pl0zikfA==} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/is-array-buffer@3.0.0': + resolution: {integrity: sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==} + engines: {node: '>=16.0.0'} + + '@smithy/md5-js@3.0.10': + resolution: {integrity: sha512-m3bv6dApflt3fS2Y1PyWPUtRP7iuBlvikEOGwu0HsCZ0vE7zcIX+dBoh3e+31/rddagw8nj92j0kJg2TfV+SJA==} + + '@smithy/middleware-content-length@3.0.12': + resolution: {integrity: sha512-1mDEXqzM20yywaMDuf5o9ue8OkJ373lSPbaSjyEvkWdqELhFMyNNgKGWL/rCSf4KME8B+HlHKuR8u9kRj8HzEQ==} + engines: {node: '>=16.0.0'} + + '@smithy/middleware-endpoint@3.2.3': + resolution: {integrity: sha512-Hdl9296i/EMptaX7agrSzJZDiz5Y8XPUeBbctTmMtnCguGpqfU3jVsTUan0VLaOhsnquqWLL8Bl5HrlbVGT1og==} + engines: {node: '>=16.0.0'} + + '@smithy/middleware-retry@3.0.27': + resolution: {integrity: sha512-H3J/PjJpLL7Tt+fxDKiOD25sMc94YetlQhCnYeNmina2LZscAdu0ZEZPas/kwePHABaEtqp7hqa5S4UJgMs1Tg==} + engines: {node: '>=16.0.0'} + + '@smithy/middleware-serde@3.0.10': + resolution: {integrity: sha512-MnAuhh+dD14F428ubSJuRnmRsfOpxSzvRhaGVTvd/lrUDE3kxzCCmH8lnVTvoNQnV2BbJ4c15QwZ3UdQBtFNZA==} + engines: {node: '>=16.0.0'} + + '@smithy/middleware-stack@3.0.10': + resolution: {integrity: sha512-grCHyoiARDBBGPyw2BeicpjgpsDFWZZxptbVKb3CRd/ZA15F/T6rZjCCuBUjJwdck1nwUuIxYtsS4H9DDpbP5w==} + engines: {node: '>=16.0.0'} + + '@smithy/node-config-provider@3.1.11': + resolution: {integrity: sha512-URq3gT3RpDikh/8MBJUB+QGZzfS7Bm6TQTqoh4CqE8NBuyPkWa5eUXj0XFcFfeZVgg3WMh1u19iaXn8FvvXxZw==} + engines: {node: '>=16.0.0'} + + '@smithy/node-http-handler@3.3.1': + resolution: {integrity: sha512-fr+UAOMGWh6bn4YSEezBCpJn9Ukp9oR4D32sCjCo7U81evE11YePOQ58ogzyfgmjIO79YeOdfXXqr0jyhPQeMg==} + engines: {node: '>=16.0.0'} + + '@smithy/property-provider@3.1.10': + resolution: {integrity: sha512-n1MJZGTorTH2DvyTVj+3wXnd4CzjJxyXeOgnTlgNVFxaaMeT4OteEp4QrzF8p9ee2yg42nvyVK6R/awLCakjeQ==} + engines: {node: '>=16.0.0'} + + '@smithy/protocol-http@4.1.7': + resolution: {integrity: sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==} + engines: {node: '>=16.0.0'} + + '@smithy/querystring-builder@3.0.10': + resolution: {integrity: sha512-nT9CQF3EIJtIUepXQuBFb8dxJi3WVZS3XfuDksxSCSn+/CzZowRLdhDn+2acbBv8R6eaJqPupoI/aRFIImNVPQ==} + engines: {node: '>=16.0.0'} + + '@smithy/querystring-parser@3.0.10': + resolution: {integrity: sha512-Oa0XDcpo9SmjhiDD9ua2UyM3uU01ZTuIrNdZvzwUTykW1PM8o2yJvMh1Do1rY5sUQg4NDV70dMi0JhDx4GyxuQ==} + engines: {node: '>=16.0.0'} + + '@smithy/service-error-classification@3.0.10': + resolution: {integrity: sha512-zHe642KCqDxXLuhs6xmHVgRwy078RfqxP2wRDpIyiF8EmsWXptMwnMwbVa50lw+WOGNrYm9zbaEg0oDe3PTtvQ==} + engines: {node: '>=16.0.0'} + + '@smithy/shared-ini-file-loader@3.1.11': + resolution: {integrity: sha512-AUdrIZHFtUgmfSN4Gq9nHu3IkHMa1YDcN+s061Nfm+6pQ0mJy85YQDB0tZBCmls0Vuj22pLwDPmL92+Hvfwwlg==} + engines: {node: '>=16.0.0'} + + '@smithy/signature-v4@4.2.3': + resolution: {integrity: sha512-pPSQQ2v2vu9vc8iew7sszLd0O09I5TRc5zhY71KA+Ao0xYazIG+uLeHbTJfIWGO3BGVLiXjUr3EEeCcEQLjpWQ==} + engines: {node: '>=16.0.0'} + + '@smithy/smithy-client@3.4.4': + resolution: {integrity: sha512-dPGoJuSZqvirBq+yROapBcHHvFjChoAQT8YPWJ820aPHHiowBlB3RL1Q4kPT1hx0qKgJuf+HhyzKi5Gbof4fNA==} + engines: {node: '>=16.0.0'} + + '@smithy/types@3.7.1': + resolution: {integrity: sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==} + engines: {node: '>=16.0.0'} + + '@smithy/url-parser@3.0.10': + resolution: {integrity: sha512-j90NUalTSBR2NaZTuruEgavSdh8MLirf58LoGSk4AtQfyIymogIhgnGUU2Mga2bkMkpSoC9gxb74xBXL5afKAQ==} + + '@smithy/util-base64@3.0.0': + resolution: {integrity: sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==} + engines: {node: '>=16.0.0'} + + '@smithy/util-body-length-browser@3.0.0': + resolution: {integrity: sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==} + + '@smithy/util-body-length-node@3.0.0': + resolution: {integrity: sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==} + engines: {node: '>=16.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-buffer-from@3.0.0': + resolution: {integrity: sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==} + engines: {node: '>=16.0.0'} + + '@smithy/util-config-provider@3.0.0': + resolution: {integrity: sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==} + engines: {node: '>=16.0.0'} + + '@smithy/util-defaults-mode-browser@3.0.27': + resolution: {integrity: sha512-GV8NvPy1vAGp7u5iD/xNKUxCorE4nQzlyl057qRac+KwpH5zq8wVq6rE3lPPeuFLyQXofPN6JwxL1N9ojGapiQ==} + engines: {node: '>= 10.0.0'} + + '@smithy/util-defaults-mode-node@3.0.27': + resolution: {integrity: sha512-7+4wjWfZqZxZVJvDutO+i1GvL6bgOajEkop4FuR6wudFlqBiqwxw3HoH6M9NgeCd37km8ga8NPp2JacQEtAMPg==} + engines: {node: '>= 10.0.0'} + + '@smithy/util-endpoints@2.1.6': + resolution: {integrity: sha512-mFV1t3ndBh0yZOJgWxO9J/4cHZVn5UG1D8DeCc6/echfNkeEJWu9LD7mgGH5fHrEdR7LDoWw7PQO6QiGpHXhgA==} + engines: {node: '>=16.0.0'} + + '@smithy/util-hex-encoding@3.0.0': + resolution: {integrity: sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==} + engines: {node: '>=16.0.0'} + + '@smithy/util-middleware@3.0.10': + resolution: {integrity: sha512-eJO+/+RsrG2RpmY68jZdwQtnfsxjmPxzMlQpnHKjFPwrYqvlcT+fHdT+ZVwcjlWSrByOhGr9Ff2GG17efc192A==} + engines: {node: '>=16.0.0'} + + '@smithy/util-retry@3.0.10': + resolution: {integrity: sha512-1l4qatFp4PiU6j7UsbasUHL2VU023NRB/gfaa1M0rDqVrRN4g3mCArLRyH3OuktApA4ye+yjWQHjdziunw2eWA==} + engines: {node: '>=16.0.0'} + + '@smithy/util-stream@3.3.1': + resolution: {integrity: sha512-Ff68R5lJh2zj+AUTvbAU/4yx+6QPRzg7+pI7M1FbtQHcRIp7xvguxVsQBKyB3fwiOwhAKu0lnNyYBaQfSW6TNw==} + engines: {node: '>=16.0.0'} + + '@smithy/util-uri-escape@3.0.0': + resolution: {integrity: sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==} + engines: {node: '>=16.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@3.0.0': + resolution: {integrity: sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==} + engines: {node: '>=16.0.0'} + + '@smithy/util-waiter@3.1.9': + resolution: {integrity: sha512-/aMXPANhMOlMPjfPtSrDfPeVP8l56SJlz93xeiLmhLe5xvlXA5T3abZ2ilEsDEPeY9T/wnN/vNGn9wa1SbufWA==} + engines: {node: '>=16.0.0'} + '@sqltools/formatter@1.2.5': resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} @@ -731,6 +1106,9 @@ packages: '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + '@types/multer@1.4.12': + resolution: {integrity: sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==} + '@types/node@20.17.6': resolution: {integrity: sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==} @@ -1028,6 +1406,9 @@ packages: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + bowser@2.11.0: + resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} @@ -1540,6 +1921,10 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + fast-xml-parser@4.4.1: + resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} + hasBin: true + fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} @@ -2790,6 +3175,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + strnum@1.0.5: + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + superagent@9.0.2: resolution: {integrity: sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==} engines: {node: '>=14.18.0'} @@ -3234,6 +3622,513 @@ snapshots: transitivePeerDependencies: - chokidar + '@aws-crypto/crc32@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.692.0 + tslib: 2.8.1 + + '@aws-crypto/crc32c@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.692.0 + tslib: 2.8.1 + + '@aws-crypto/sha1-browser@5.2.0': + dependencies: + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.692.0 + '@aws-sdk/util-locate-window': 3.693.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.692.0 + '@aws-sdk/util-locate-window': 3.693.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.692.0 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.692.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-s3@3.693.0': + dependencies: + '@aws-crypto/sha1-browser': 5.2.0 + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/client-sso-oidc': 3.693.0(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/client-sts': 3.693.0 + '@aws-sdk/core': 3.693.0 + '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/middleware-bucket-endpoint': 3.693.0 + '@aws-sdk/middleware-expect-continue': 3.693.0 + '@aws-sdk/middleware-flexible-checksums': 3.693.0 + '@aws-sdk/middleware-host-header': 3.693.0 + '@aws-sdk/middleware-location-constraint': 3.693.0 + '@aws-sdk/middleware-logger': 3.693.0 + '@aws-sdk/middleware-recursion-detection': 3.693.0 + '@aws-sdk/middleware-sdk-s3': 3.693.0 + '@aws-sdk/middleware-ssec': 3.693.0 + '@aws-sdk/middleware-user-agent': 3.693.0 + '@aws-sdk/region-config-resolver': 3.693.0 + '@aws-sdk/signature-v4-multi-region': 3.693.0 + '@aws-sdk/types': 3.692.0 + '@aws-sdk/util-endpoints': 3.693.0 + '@aws-sdk/util-user-agent-browser': 3.693.0 + '@aws-sdk/util-user-agent-node': 3.693.0 + '@aws-sdk/xml-builder': 3.693.0 + '@smithy/config-resolver': 3.0.12 + '@smithy/core': 2.5.3 + '@smithy/eventstream-serde-browser': 3.0.13 + '@smithy/eventstream-serde-config-resolver': 3.0.10 + '@smithy/eventstream-serde-node': 3.0.12 + '@smithy/fetch-http-handler': 4.1.1 + '@smithy/hash-blob-browser': 3.1.9 + '@smithy/hash-node': 3.0.10 + '@smithy/hash-stream-node': 3.1.9 + '@smithy/invalid-dependency': 3.0.10 + '@smithy/md5-js': 3.0.10 + '@smithy/middleware-content-length': 3.0.12 + '@smithy/middleware-endpoint': 3.2.3 + '@smithy/middleware-retry': 3.0.27 + '@smithy/middleware-serde': 3.0.10 + '@smithy/middleware-stack': 3.0.10 + '@smithy/node-config-provider': 3.1.11 + '@smithy/node-http-handler': 3.3.1 + '@smithy/protocol-http': 4.1.7 + '@smithy/smithy-client': 3.4.4 + '@smithy/types': 3.7.1 + '@smithy/url-parser': 3.0.10 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.27 + '@smithy/util-defaults-mode-node': 3.0.27 + '@smithy/util-endpoints': 2.1.6 + '@smithy/util-middleware': 3.0.10 + '@smithy/util-retry': 3.0.10 + '@smithy/util-stream': 3.3.1 + '@smithy/util-utf8': 3.0.0 + '@smithy/util-waiter': 3.1.9 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/client-sts': 3.693.0 + '@aws-sdk/core': 3.693.0 + '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/middleware-host-header': 3.693.0 + '@aws-sdk/middleware-logger': 3.693.0 + '@aws-sdk/middleware-recursion-detection': 3.693.0 + '@aws-sdk/middleware-user-agent': 3.693.0 + '@aws-sdk/region-config-resolver': 3.693.0 + '@aws-sdk/types': 3.692.0 + '@aws-sdk/util-endpoints': 3.693.0 + '@aws-sdk/util-user-agent-browser': 3.693.0 + '@aws-sdk/util-user-agent-node': 3.693.0 + '@smithy/config-resolver': 3.0.12 + '@smithy/core': 2.5.3 + '@smithy/fetch-http-handler': 4.1.1 + '@smithy/hash-node': 3.0.10 + '@smithy/invalid-dependency': 3.0.10 + '@smithy/middleware-content-length': 3.0.12 + '@smithy/middleware-endpoint': 3.2.3 + '@smithy/middleware-retry': 3.0.27 + '@smithy/middleware-serde': 3.0.10 + '@smithy/middleware-stack': 3.0.10 + '@smithy/node-config-provider': 3.1.11 + '@smithy/node-http-handler': 3.3.1 + '@smithy/protocol-http': 4.1.7 + '@smithy/smithy-client': 3.4.4 + '@smithy/types': 3.7.1 + '@smithy/url-parser': 3.0.10 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.27 + '@smithy/util-defaults-mode-node': 3.0.27 + '@smithy/util-endpoints': 2.1.6 + '@smithy/util-middleware': 3.0.10 + '@smithy/util-retry': 3.0.10 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sso@3.693.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.693.0 + '@aws-sdk/middleware-host-header': 3.693.0 + '@aws-sdk/middleware-logger': 3.693.0 + '@aws-sdk/middleware-recursion-detection': 3.693.0 + '@aws-sdk/middleware-user-agent': 3.693.0 + '@aws-sdk/region-config-resolver': 3.693.0 + '@aws-sdk/types': 3.692.0 + '@aws-sdk/util-endpoints': 3.693.0 + '@aws-sdk/util-user-agent-browser': 3.693.0 + '@aws-sdk/util-user-agent-node': 3.693.0 + '@smithy/config-resolver': 3.0.12 + '@smithy/core': 2.5.3 + '@smithy/fetch-http-handler': 4.1.1 + '@smithy/hash-node': 3.0.10 + '@smithy/invalid-dependency': 3.0.10 + '@smithy/middleware-content-length': 3.0.12 + '@smithy/middleware-endpoint': 3.2.3 + '@smithy/middleware-retry': 3.0.27 + '@smithy/middleware-serde': 3.0.10 + '@smithy/middleware-stack': 3.0.10 + '@smithy/node-config-provider': 3.1.11 + '@smithy/node-http-handler': 3.3.1 + '@smithy/protocol-http': 4.1.7 + '@smithy/smithy-client': 3.4.4 + '@smithy/types': 3.7.1 + '@smithy/url-parser': 3.0.10 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.27 + '@smithy/util-defaults-mode-node': 3.0.27 + '@smithy/util-endpoints': 2.1.6 + '@smithy/util-middleware': 3.0.10 + '@smithy/util-retry': 3.0.10 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sts@3.693.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/client-sso-oidc': 3.693.0(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/core': 3.693.0 + '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/middleware-host-header': 3.693.0 + '@aws-sdk/middleware-logger': 3.693.0 + '@aws-sdk/middleware-recursion-detection': 3.693.0 + '@aws-sdk/middleware-user-agent': 3.693.0 + '@aws-sdk/region-config-resolver': 3.693.0 + '@aws-sdk/types': 3.692.0 + '@aws-sdk/util-endpoints': 3.693.0 + '@aws-sdk/util-user-agent-browser': 3.693.0 + '@aws-sdk/util-user-agent-node': 3.693.0 + '@smithy/config-resolver': 3.0.12 + '@smithy/core': 2.5.3 + '@smithy/fetch-http-handler': 4.1.1 + '@smithy/hash-node': 3.0.10 + '@smithy/invalid-dependency': 3.0.10 + '@smithy/middleware-content-length': 3.0.12 + '@smithy/middleware-endpoint': 3.2.3 + '@smithy/middleware-retry': 3.0.27 + '@smithy/middleware-serde': 3.0.10 + '@smithy/middleware-stack': 3.0.10 + '@smithy/node-config-provider': 3.1.11 + '@smithy/node-http-handler': 3.3.1 + '@smithy/protocol-http': 4.1.7 + '@smithy/smithy-client': 3.4.4 + '@smithy/types': 3.7.1 + '@smithy/url-parser': 3.0.10 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.27 + '@smithy/util-defaults-mode-node': 3.0.27 + '@smithy/util-endpoints': 2.1.6 + '@smithy/util-middleware': 3.0.10 + '@smithy/util-retry': 3.0.10 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/core@3.693.0': + dependencies: + '@aws-sdk/types': 3.692.0 + '@smithy/core': 2.5.3 + '@smithy/node-config-provider': 3.1.11 + '@smithy/property-provider': 3.1.10 + '@smithy/protocol-http': 4.1.7 + '@smithy/signature-v4': 4.2.3 + '@smithy/smithy-client': 3.4.4 + '@smithy/types': 3.7.1 + '@smithy/util-middleware': 3.0.10 + fast-xml-parser: 4.4.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.693.0': + dependencies: + '@aws-sdk/core': 3.693.0 + '@aws-sdk/types': 3.692.0 + '@smithy/property-provider': 3.1.10 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.693.0': + dependencies: + '@aws-sdk/core': 3.693.0 + '@aws-sdk/types': 3.692.0 + '@smithy/fetch-http-handler': 4.1.1 + '@smithy/node-http-handler': 3.3.1 + '@smithy/property-provider': 3.1.10 + '@smithy/protocol-http': 4.1.7 + '@smithy/smithy-client': 3.4.4 + '@smithy/types': 3.7.1 + '@smithy/util-stream': 3.3.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0)': + dependencies: + '@aws-sdk/client-sts': 3.693.0 + '@aws-sdk/core': 3.693.0 + '@aws-sdk/credential-provider-env': 3.693.0 + '@aws-sdk/credential-provider-http': 3.693.0 + '@aws-sdk/credential-provider-process': 3.693.0 + '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)) + '@aws-sdk/credential-provider-web-identity': 3.693.0(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/types': 3.692.0 + '@smithy/credential-provider-imds': 3.2.7 + '@smithy/property-provider': 3.1.10 + '@smithy/shared-ini-file-loader': 3.1.11 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + + '@aws-sdk/credential-provider-node@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0)': + dependencies: + '@aws-sdk/credential-provider-env': 3.693.0 + '@aws-sdk/credential-provider-http': 3.693.0 + '@aws-sdk/credential-provider-ini': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-process': 3.693.0 + '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)) + '@aws-sdk/credential-provider-web-identity': 3.693.0(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/types': 3.692.0 + '@smithy/credential-provider-imds': 3.2.7 + '@smithy/property-provider': 3.1.10 + '@smithy/shared-ini-file-loader': 3.1.11 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - '@aws-sdk/client-sts' + - aws-crt + + '@aws-sdk/credential-provider-process@3.693.0': + dependencies: + '@aws-sdk/core': 3.693.0 + '@aws-sdk/types': 3.692.0 + '@smithy/property-provider': 3.1.10 + '@smithy/shared-ini-file-loader': 3.1.11 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))': + dependencies: + '@aws-sdk/client-sso': 3.693.0 + '@aws-sdk/core': 3.693.0 + '@aws-sdk/token-providers': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)) + '@aws-sdk/types': 3.692.0 + '@smithy/property-provider': 3.1.10 + '@smithy/shared-ini-file-loader': 3.1.11 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.693.0(@aws-sdk/client-sts@3.693.0)': + dependencies: + '@aws-sdk/client-sts': 3.693.0 + '@aws-sdk/core': 3.693.0 + '@aws-sdk/types': 3.692.0 + '@smithy/property-provider': 3.1.10 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-bucket-endpoint@3.693.0': + dependencies: + '@aws-sdk/types': 3.692.0 + '@aws-sdk/util-arn-parser': 3.693.0 + '@smithy/node-config-provider': 3.1.11 + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + '@smithy/util-config-provider': 3.0.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-expect-continue@3.693.0': + dependencies: + '@aws-sdk/types': 3.692.0 + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-flexible-checksums@3.693.0': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@aws-crypto/crc32c': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/core': 3.693.0 + '@aws-sdk/types': 3.692.0 + '@smithy/is-array-buffer': 3.0.0 + '@smithy/node-config-provider': 3.1.11 + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + '@smithy/util-middleware': 3.0.10 + '@smithy/util-stream': 3.3.1 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-host-header@3.693.0': + dependencies: + '@aws-sdk/types': 3.692.0 + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-location-constraint@3.693.0': + dependencies: + '@aws-sdk/types': 3.692.0 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-logger@3.693.0': + dependencies: + '@aws-sdk/types': 3.692.0 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-recursion-detection@3.693.0': + dependencies: + '@aws-sdk/types': 3.692.0 + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-sdk-s3@3.693.0': + dependencies: + '@aws-sdk/core': 3.693.0 + '@aws-sdk/types': 3.692.0 + '@aws-sdk/util-arn-parser': 3.693.0 + '@smithy/core': 2.5.3 + '@smithy/node-config-provider': 3.1.11 + '@smithy/protocol-http': 4.1.7 + '@smithy/signature-v4': 4.2.3 + '@smithy/smithy-client': 3.4.4 + '@smithy/types': 3.7.1 + '@smithy/util-config-provider': 3.0.0 + '@smithy/util-middleware': 3.0.10 + '@smithy/util-stream': 3.3.1 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-ssec@3.693.0': + dependencies: + '@aws-sdk/types': 3.692.0 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/middleware-user-agent@3.693.0': + dependencies: + '@aws-sdk/core': 3.693.0 + '@aws-sdk/types': 3.692.0 + '@aws-sdk/util-endpoints': 3.693.0 + '@smithy/core': 2.5.3 + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/region-config-resolver@3.693.0': + dependencies: + '@aws-sdk/types': 3.692.0 + '@smithy/node-config-provider': 3.1.11 + '@smithy/types': 3.7.1 + '@smithy/util-config-provider': 3.0.0 + '@smithy/util-middleware': 3.0.10 + tslib: 2.8.1 + + '@aws-sdk/signature-v4-multi-region@3.693.0': + dependencies: + '@aws-sdk/middleware-sdk-s3': 3.693.0 + '@aws-sdk/types': 3.692.0 + '@smithy/protocol-http': 4.1.7 + '@smithy/signature-v4': 4.2.3 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))': + dependencies: + '@aws-sdk/client-sso-oidc': 3.693.0(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/types': 3.692.0 + '@smithy/property-provider': 3.1.10 + '@smithy/shared-ini-file-loader': 3.1.11 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/types@3.692.0': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/util-arn-parser@3.693.0': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-endpoints@3.693.0': + dependencies: + '@aws-sdk/types': 3.692.0 + '@smithy/types': 3.7.1 + '@smithy/util-endpoints': 2.1.6 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.693.0': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-browser@3.693.0': + dependencies: + '@aws-sdk/types': 3.692.0 + '@smithy/types': 3.7.1 + bowser: 2.11.0 + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-node@3.693.0': + dependencies: + '@aws-sdk/middleware-user-agent': 3.693.0 + '@aws-sdk/types': 3.692.0 + '@smithy/node-config-provider': 3.1.11 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.693.0': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + '@babel/code-frame@7.26.2': dependencies: '@babel/helper-validator-identifier': 7.25.9 @@ -3870,6 +4765,337 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@smithy/abort-controller@3.1.8': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/chunked-blob-reader-native@3.0.1': + dependencies: + '@smithy/util-base64': 3.0.0 + tslib: 2.8.1 + + '@smithy/chunked-blob-reader@4.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/config-resolver@3.0.12': + dependencies: + '@smithy/node-config-provider': 3.1.11 + '@smithy/types': 3.7.1 + '@smithy/util-config-provider': 3.0.0 + '@smithy/util-middleware': 3.0.10 + tslib: 2.8.1 + + '@smithy/core@2.5.3': + dependencies: + '@smithy/middleware-serde': 3.0.10 + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-middleware': 3.0.10 + '@smithy/util-stream': 3.3.1 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@3.2.7': + dependencies: + '@smithy/node-config-provider': 3.1.11 + '@smithy/property-provider': 3.1.10 + '@smithy/types': 3.7.1 + '@smithy/url-parser': 3.0.10 + tslib: 2.8.1 + + '@smithy/eventstream-codec@3.1.9': + dependencies: + '@aws-crypto/crc32': 5.2.0 + '@smithy/types': 3.7.1 + '@smithy/util-hex-encoding': 3.0.0 + tslib: 2.8.1 + + '@smithy/eventstream-serde-browser@3.0.13': + dependencies: + '@smithy/eventstream-serde-universal': 3.0.12 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-config-resolver@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-node@3.0.12': + dependencies: + '@smithy/eventstream-serde-universal': 3.0.12 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/eventstream-serde-universal@3.0.12': + dependencies: + '@smithy/eventstream-codec': 3.1.9 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@4.1.1': + dependencies: + '@smithy/protocol-http': 4.1.7 + '@smithy/querystring-builder': 3.0.10 + '@smithy/types': 3.7.1 + '@smithy/util-base64': 3.0.0 + tslib: 2.8.1 + + '@smithy/hash-blob-browser@3.1.9': + dependencies: + '@smithy/chunked-blob-reader': 4.0.0 + '@smithy/chunked-blob-reader-native': 3.0.1 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/hash-node@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + '@smithy/util-buffer-from': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + + '@smithy/hash-stream-node@3.1.9': + dependencies: + '@smithy/types': 3.7.1 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + + '@smithy/invalid-dependency@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/is-array-buffer@3.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/md5-js@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + + '@smithy/middleware-content-length@3.0.12': + dependencies: + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/middleware-endpoint@3.2.3': + dependencies: + '@smithy/core': 2.5.3 + '@smithy/middleware-serde': 3.0.10 + '@smithy/node-config-provider': 3.1.11 + '@smithy/shared-ini-file-loader': 3.1.11 + '@smithy/types': 3.7.1 + '@smithy/url-parser': 3.0.10 + '@smithy/util-middleware': 3.0.10 + tslib: 2.8.1 + + '@smithy/middleware-retry@3.0.27': + dependencies: + '@smithy/node-config-provider': 3.1.11 + '@smithy/protocol-http': 4.1.7 + '@smithy/service-error-classification': 3.0.10 + '@smithy/smithy-client': 3.4.4 + '@smithy/types': 3.7.1 + '@smithy/util-middleware': 3.0.10 + '@smithy/util-retry': 3.0.10 + tslib: 2.8.1 + uuid: 9.0.1 + + '@smithy/middleware-serde@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/middleware-stack@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/node-config-provider@3.1.11': + dependencies: + '@smithy/property-provider': 3.1.10 + '@smithy/shared-ini-file-loader': 3.1.11 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/node-http-handler@3.3.1': + dependencies: + '@smithy/abort-controller': 3.1.8 + '@smithy/protocol-http': 4.1.7 + '@smithy/querystring-builder': 3.0.10 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/property-provider@3.1.10': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/protocol-http@4.1.7': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/querystring-builder@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + '@smithy/util-uri-escape': 3.0.0 + tslib: 2.8.1 + + '@smithy/querystring-parser@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/service-error-classification@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + + '@smithy/shared-ini-file-loader@3.1.11': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/signature-v4@4.2.3': + dependencies: + '@smithy/is-array-buffer': 3.0.0 + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + '@smithy/util-hex-encoding': 3.0.0 + '@smithy/util-middleware': 3.0.10 + '@smithy/util-uri-escape': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + + '@smithy/smithy-client@3.4.4': + dependencies: + '@smithy/core': 2.5.3 + '@smithy/middleware-endpoint': 3.2.3 + '@smithy/middleware-stack': 3.0.10 + '@smithy/protocol-http': 4.1.7 + '@smithy/types': 3.7.1 + '@smithy/util-stream': 3.3.1 + tslib: 2.8.1 + + '@smithy/types@3.7.1': + dependencies: + tslib: 2.8.1 + + '@smithy/url-parser@3.0.10': + dependencies: + '@smithy/querystring-parser': 3.0.10 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/util-base64@3.0.0': + dependencies: + '@smithy/util-buffer-from': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + + '@smithy/util-body-length-browser@3.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-body-length-node@3.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-buffer-from@3.0.0': + dependencies: + '@smithy/is-array-buffer': 3.0.0 + tslib: 2.8.1 + + '@smithy/util-config-provider@3.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-defaults-mode-browser@3.0.27': + dependencies: + '@smithy/property-provider': 3.1.10 + '@smithy/smithy-client': 3.4.4 + '@smithy/types': 3.7.1 + bowser: 2.11.0 + tslib: 2.8.1 + + '@smithy/util-defaults-mode-node@3.0.27': + dependencies: + '@smithy/config-resolver': 3.0.12 + '@smithy/credential-provider-imds': 3.2.7 + '@smithy/node-config-provider': 3.1.11 + '@smithy/property-provider': 3.1.10 + '@smithy/smithy-client': 3.4.4 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/util-endpoints@2.1.6': + dependencies: + '@smithy/node-config-provider': 3.1.11 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/util-hex-encoding@3.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-middleware@3.0.10': + dependencies: + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/util-retry@3.0.10': + dependencies: + '@smithy/service-error-classification': 3.0.10 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + + '@smithy/util-stream@3.3.1': + dependencies: + '@smithy/fetch-http-handler': 4.1.1 + '@smithy/node-http-handler': 3.3.1 + '@smithy/types': 3.7.1 + '@smithy/util-base64': 3.0.0 + '@smithy/util-buffer-from': 3.0.0 + '@smithy/util-hex-encoding': 3.0.0 + '@smithy/util-utf8': 3.0.0 + tslib: 2.8.1 + + '@smithy/util-uri-escape@3.0.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-utf8@3.0.0': + dependencies: + '@smithy/util-buffer-from': 3.0.0 + tslib: 2.8.1 + + '@smithy/util-waiter@3.1.9': + dependencies: + '@smithy/abort-controller': 3.1.8 + '@smithy/types': 3.7.1 + tslib: 2.8.1 + '@sqltools/formatter@1.2.5': {} '@tsconfig/node10@1.0.11': {} @@ -3969,6 +5195,10 @@ snapshots: '@types/mime@1.3.5': {} + '@types/multer@1.4.12': + dependencies: + '@types/express': 5.0.0 + '@types/node@20.17.6': dependencies: undici-types: 6.19.8 @@ -4349,6 +5579,8 @@ snapshots: transitivePeerDependencies: - supports-color + bowser@2.11.0: {} + brace-expansion@1.1.11: dependencies: balanced-match: 1.0.2 @@ -4876,6 +6108,10 @@ snapshots: fast-safe-stringify@2.1.1: {} + fast-xml-parser@4.4.1: + dependencies: + strnum: 1.0.5 + fastq@1.17.1: dependencies: reusify: 1.0.4 @@ -6288,6 +7524,8 @@ snapshots: strip-json-comments@3.1.1: {} + strnum@1.0.5: {} + superagent@9.0.2: dependencies: component-emitter: 1.3.1 diff --git a/src/app.module.ts b/src/app.module.ts index e9fd6e7..7353383 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -10,7 +10,6 @@ import { AuthModule } from './auth/auth.module'; import { CategoryModule } from './category/category.module'; import { ChapterModule } from './chapter/chapter.module'; import { CourseModuleModule } from './course-module/course-module.module'; -import { Course } from './course/course.entity'; import { CourseModule } from './course/course.module'; import { DatabaseModule } from './database/database.module'; import { databaseConfig } from './shared/configs/database.config'; @@ -21,49 +20,51 @@ import { UserStreak } from './user-streak/user-streak.entity'; import { UserStreakModule } from './user-streak/user-streak.module'; import { User } from './user/user.entity'; import { UserModule } from './user/user.module'; +import { FileModule } from './file/file.module'; const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); @Module({ - imports: [ - forFeatures, - AuthModule, - ConfigModule.forRoot({ - isGlobal: true, - validationSchema: dotenvConfig, - }), - TypeOrmModule.forRootAsync({ - imports: [ConfigModule], - useFactory: (configService: ConfigService) => ({ - ...databaseConfig, - migrations: ['dist/database/migrations/*.js'], - migrationsRun: true, - synchronize: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), - }), - inject: [ConfigService], - }), - JwtModule.register({ - global: true, - }), - DatabaseModule, - UserModule, - UserStreakModule, - CategoryModule, - CourseModule, - CourseModuleModule, - ChapterModule, - ], - controllers: [AppController], - providers: [ - AppService, - { - provide: APP_GUARD, - useClass: AuthGuard, - }, - { - provide: APP_GUARD, - useClass: RolesGuard, - }, - ], + imports: [ + forFeatures, + AuthModule, + ConfigModule.forRoot({ + isGlobal: true, + validationSchema: dotenvConfig, + }), + TypeOrmModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + ...databaseConfig, + migrations: ['dist/database/migrations/*.js'], + migrationsRun: true, + synchronize: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), + }), + inject: [ConfigService], + }), + JwtModule.register({ + global: true, + }), + DatabaseModule, + UserModule, + UserStreakModule, + CategoryModule, + CourseModule, + CourseModuleModule, + ChapterModule, + FileModule, + ], + controllers: [AppController], + providers: [ + AppService, + { + provide: APP_GUARD, + useClass: AuthGuard, + }, + { + provide: APP_GUARD, + useClass: RolesGuard, + }, + ], }) -export class AppModule {} +export class AppModule { } diff --git a/src/file/enums/folder.enum.ts b/src/file/enums/folder.enum.ts new file mode 100644 index 0000000..865ebfb --- /dev/null +++ b/src/file/enums/folder.enum.ts @@ -0,0 +1,6 @@ +export enum Folder { + CHAPTER_VIDEOS = 'chapter-videos', + COURSE_THUMBNAILS = 'course-thumbnails', + PROFILES = 'profiles', + REWARD_THUMBNAILS = 'reward-thumbnails', +} \ No newline at end of file diff --git a/src/file/file.module.ts b/src/file/file.module.ts new file mode 100644 index 0000000..d05f82f --- /dev/null +++ b/src/file/file.module.ts @@ -0,0 +1,8 @@ +import { Module } from "@nestjs/common"; +import { FileService } from "./file.service"; + +@Module({ + providers: [FileService], + exports: [FileService] +}) +export class FileModule { } \ No newline at end of file diff --git a/src/file/file.service.ts b/src/file/file.service.ts new file mode 100644 index 0000000..0193e90 --- /dev/null +++ b/src/file/file.service.ts @@ -0,0 +1,66 @@ +import { Injectable, InternalServerErrorException, NotFoundException } from "@nestjs/common"; +import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, GetObjectCommandOutput } from "@aws-sdk/client-s3"; +import { ConfigService } from "@nestjs/config"; +import { GLOBAL_CONFIG } from "src/shared/constants/global-config.constant"; +import { Folder } from "./enums/folder.enum"; +import { NodeJsClient } from "@smithy/types"; + +@Injectable() +export class FileService { + constructor( + private readonly configService: ConfigService, + ) { } + + private readonly s3Client = new S3Client({ + region: this.configService.getOrThrow(GLOBAL_CONFIG.AWS_REGION), + }) as NodeJsClient; + + async upload(folder: Folder, key: string, file: Express.Multer.File): Promise { + try { + await this.s3Client.send( + new PutObjectCommand({ + Bucket: this.configService.getOrThrow(GLOBAL_CONFIG.AWS_BUCKET_NAME), + Key: `${folder}/${key}.${file.originalname.split('.').pop()}`, + Body: file.buffer, + }) + ) + } catch (error) { + if (error instanceof Error) + throw new InternalServerErrorException(error.message); + } + } + + async get(folder: Folder, key: string): Promise { + try { + const result: GetObjectCommandOutput = await this.s3Client.send( + new GetObjectCommand({ + Bucket: this.configService.getOrThrow(GLOBAL_CONFIG.AWS_BUCKET_NAME), + Key: `${folder}/${key}`, + }) + ) + return await result.Body.transformToByteArray(); + } catch (error) { + if (error instanceof Error) + throw new NotFoundException(error.message); + } + } + + async delete(folder: Folder, key: string): Promise { + try { + await this.s3Client.send( + new DeleteObjectCommand({ + Bucket: this.configService.getOrThrow(GLOBAL_CONFIG.AWS_BUCKET_NAME), + Key: `${folder}/${key}`, + }) + ) + } catch (error) { + if (error instanceof Error) + throw new NotFoundException(error.message); + } + } + + async update(folder: Folder, key: string, file: Express.Multer.File): Promise { + await this.delete(folder, key); + await this.upload(folder, key, file); + } +} \ No newline at end of file diff --git a/src/shared/configs/dotenv.config.ts b/src/shared/configs/dotenv.config.ts index fc95c5a..14f5617 100644 --- a/src/shared/configs/dotenv.config.ts +++ b/src/shared/configs/dotenv.config.ts @@ -21,4 +21,8 @@ export const dotenvConfig = Joi.object({ JWT_REFRESH_SECRET: Joi.string().required(), JWT_ACCESS_EXPIRATION: Joi.string().required(), JWT_REFRESH_EXPIRATION: Joi.string().required(), + AWS_ACCESS_KEY_ID: Joi.string().required(), + AWS_SECRET_ACCESS_KEY: Joi.string().required(), + AWS_REGION: Joi.string().required(), + AWS_BUCKET_NAME: Joi.string().required(), }); diff --git a/src/shared/constants/global-config.constant.ts b/src/shared/constants/global-config.constant.ts index d3d5d90..8c851a7 100644 --- a/src/shared/constants/global-config.constant.ts +++ b/src/shared/constants/global-config.constant.ts @@ -12,4 +12,8 @@ export const GLOBAL_CONFIG = { JWT_REFRESH_SECRET: 'JWT_REFRESH_SECRET', JWT_ACCESS_EXPIRATION: 'JWT_ACCESS_EXPIRATION', JWT_REFRESH_EXPIRATION: 'JWT_REFRESH_EXPIRATION', + AWS_ACCESS_KEY_ID: "AWS_ACCESS_KEY_ID", + AWS_SECRET_ACCESS_KEY: "AWS_SECRET_ACCESS_KEY", + AWS_REGION: "AWS_REGION", + AWS_BUCKET_NAME: "AWS_BUCKET_NAME", }; From 499d8076e23eb8e5a6bfbb553f1cff8c4a3475fc Mon Sep 17 00:00:00 2001 From: ganthepro Date: Fri, 15 Nov 2024 15:01:17 +0700 Subject: [PATCH 035/155] feat: integrate FileModule into UserModule and add profileKey to User entity --- src/user/user.controller.ts | 351 ++++++++++++++++++++++++------------ src/user/user.entity.ts | 109 +++++------ src/user/user.module.ts | 14 +- src/user/user.service.ts | 105 ++++++----- 4 files changed, 349 insertions(+), 230 deletions(-) diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index ccfe5cc..8602498 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -1,23 +1,29 @@ import { - Body, - Controller, - Delete, - Get, - HttpCode, - HttpStatus, - Injectable, - Param, - ParseUUIDPipe, - Patch, - Query, - Req, + Body, + Controller, + Delete, + Get, + HttpCode, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Query, + Req, + StreamableFile, + UseInterceptors, + UploadedFile, + ParseFilePipeBuilder, } from '@nestjs/common'; import { - ApiBearerAuth, - ApiParam, - ApiQuery, - ApiResponse, - ApiTags, + ApiBearerAuth, + ApiBody, + ApiConsumes, + ApiParam, + ApiQuery, + ApiResponse, + ApiTags, } from '@nestjs/swagger'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { Public } from 'src/shared/decorators/public.decorator'; @@ -26,118 +32,225 @@ import { Role } from 'src/shared/enums/roles.enum'; import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { UpdateUserDto } from './dtos/update-user.dto'; import { - PaginatedUserResponseDto, - UserResponseDto, + PaginatedUserResponseDto, + UserResponseDto, } from './dtos/user-response.dto'; import { UserService } from './user.service'; +import { FileService } from 'src/file/file.service'; +import { Folder } from 'src/file/enums/folder.enum'; +import { hash } from 'argon2'; +import { FileInterceptor } from '@nestjs/platform-express'; @Controller('user') @ApiTags('User') -@ApiBearerAuth() @Injectable() export class UserController { - constructor(private readonly userService: UserService) {} + constructor( + private readonly userService: UserService, + private readonly fileService: FileService, + ) { } - @Get('profile') - @ApiResponse({ - status: HttpStatus.OK, - type: UserResponseDto, - description: 'Get user profile', - }) - async getProfile( - @Req() request: AuthenticatedRequest, - ): Promise { - const user = await this.userService.findOne({ - where: { id: request.user.id }, - }); - return new UserResponseDto(user); - } + @Get(':id/avatar') + @Public() + @ApiParam({ + name: 'id', + type: String, + description: 'User id', + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Get user avatar', + type: StreamableFile, + }) + async getAvatar( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const user = await this.userService.findOne({ where: { id } }); + const file = await this.fileService.get( + Folder.PROFILES, user.profileKey); + return new StreamableFile(file, { + disposition: 'inline', + type: `image/${user.profileKey.split('.').pop()}`, + }); + } - @Get() - @Roles(Role.ADMIN) - @ApiResponse({ - status: HttpStatus.OK, - type: PaginatedUserResponseDto, - description: 'Get all users', - }) - @ApiQuery({ - name: 'page', - type: Number, - required: false, - description: 'Page number', - }) - @ApiQuery({ - name: 'limit', - type: Number, - required: false, - description: 'Items per page', - }) - @ApiQuery({ - name: 'search', - type: String, - required: false, - description: 'Search by email', - }) - async findAll( - @Query() query: PaginateQueryDto, - ): Promise { - return this.userService.findAll({ - page: query.page, - limit: query.limit, - search: query.search, - }); - } + @Patch('avatar') + @ApiBearerAuth() + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'User avatar updated successfully', + }) + @HttpCode(HttpStatus.NO_CONTENT) + @UseInterceptors(FileInterceptor('file')) + @ApiConsumes('multipart/form-data') + @ApiBody({ + schema: { + type: 'object', + properties: { + file: { + type: 'string', + format: 'binary', + }, + }, + }, + }) + async uploadProfileAvatar( + @Req() request: AuthenticatedRequest, + @UploadedFile( + new ParseFilePipeBuilder() + .addFileTypeValidator({ fileType: 'image/*' }) + .build({ + fileIsRequired: true, + errorHttpStatusCode: HttpStatus.BAD_REQUEST, + }), + ) + file: Express.Multer.File, + ): Promise { + const user = await this.userService.findOne({ + where: { id: request.user.id }, + }) + if (user.profileKey) + await this.fileService.update(Folder.PROFILES, user.id, file); + else { + await this.fileService.upload(Folder.PROFILES, user.id, file); + await this.userService.update(user.id, { profileKey: `${user.id}.${file.mimetype.split('/').pop()}` }); + } + } - @Get(':id') - @ApiParam({ - name: 'id', - type: String, - description: 'User id', - }) - @ApiResponse({ - status: HttpStatus.OK, - type: UserResponseDto, - description: 'Get user by id', - }) - @Public() - async findOne( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - ): Promise { - const user = await this.userService.findOne({ where: { id } }); - return new UserResponseDto(user); - } + @Get('avatar') + @ApiBearerAuth() + @ApiResponse({ + status: HttpStatus.OK, + description: 'Get user avatar', + type: StreamableFile, + }) + async getProfileAvatar( + @Req() request: AuthenticatedRequest, + ): Promise { + const user = await this.userService.findOne({ + where: { id: request.user.id }, + }); + const file = await this.fileService.get( + Folder.PROFILES, user.profileKey); + return new StreamableFile(file, { + disposition: 'inline', + type: `image/${user.profileKey.split('.').pop()}`, + }); + } - @Patch() - @ApiResponse({ - status: HttpStatus.OK, - type: UserResponseDto, - description: 'Update user', - }) - async update( - @Req() request: AuthenticatedRequest, - @Body() updateUserDto: UpdateUserDto, - ): Promise { - const user = await this.userService.update(request.user.id, updateUserDto); - return new UserResponseDto(user); - } + @Get('profile') + @ApiBearerAuth() + @ApiResponse({ + status: HttpStatus.OK, + type: UserResponseDto, + description: 'Get user profile', + }) + async getProfile( + @Req() request: AuthenticatedRequest, + ): Promise { + const user = await this.userService.findOne({ + where: { id: request.user.id }, + }); + return new UserResponseDto(user); + } - @Delete() - @ApiResponse({ - status: HttpStatus.NO_CONTENT, - description: 'Delete user', - }) - @HttpCode(HttpStatus.NO_CONTENT) - async delete( - @Req() request: AuthenticatedRequest, - ): Promise<{ massage: string }> { - await this.userService.delete(request.user.id); - return { massage: 'User deleted successfully' }; - } + @Get() + @Roles(Role.ADMIN) + @ApiBearerAuth() + @ApiResponse({ + status: HttpStatus.OK, + type: PaginatedUserResponseDto, + description: 'Get all users', + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by email', + }) + async findAll( + @Query() query: PaginateQueryDto, + ): Promise { + return this.userService.findAll({ + page: query.page, + limit: query.limit, + search: query.search, + }); + } + + @Get(':id') + @ApiParam({ + name: 'id', + type: String, + description: 'User id', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: UserResponseDto, + description: 'Get user by id', + }) + @Public() + async findOne( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const user = await this.userService.findOne({ where: { id } }); + return new UserResponseDto(user); + } + + @Patch() + @ApiBearerAuth() + @ApiResponse({ + status: HttpStatus.OK, + type: UserResponseDto, + description: 'Update user', + }) + async update( + @Req() request: AuthenticatedRequest, + @Body() updateUserDto: UpdateUserDto, + ): Promise { + if (updateUserDto.password) + updateUserDto.password = await hash(updateUserDto.password); + const user = await this.userService.update(request.user.id, updateUserDto); + return new UserResponseDto(user); + } + + @Delete() + @ApiBearerAuth() + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Delete user', + }) + @HttpCode(HttpStatus.NO_CONTENT) + async delete( + @Req() request: AuthenticatedRequest, + ): Promise<{ massage: string }> { + await this.userService.delete(request.user.id); + return { massage: 'User deleted successfully' }; + } } diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index 5faf545..bc6a198 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -1,61 +1,66 @@ import { Entity, OneToMany } from 'typeorm'; import { - Column, - PrimaryGeneratedColumn, - CreateDateColumn, - UpdateDateColumn, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, } from 'typeorm'; import { Role } from 'src/shared/enums/roles.enum'; import { Course } from 'src/course/course.entity'; @Entity() export class User { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ - nullable: false, - unique: true, - }) - username: string; - - @Column({ - nullable: false, - }) - fullname: string; - - @Column({ - type: 'enum', - enum: Role, - nullable: false, - default: Role.STUDENT, - }) - role: Role; - - @Column({ - nullable: false, - unique: true, - }) - password: string; - - @Column({ - nullable: false, - unique: true, - }) - email: string; - - @OneToMany(() => Course, (course) => course.teacher) - courses: Course[]; - - @CreateDateColumn({ - type: 'timestamp with time zone', - nullable: false, - }) - createdAt: Date; - - @UpdateDateColumn({ - type: 'timestamp with time zone', - nullable: false, - }) - updatedAt: Date; + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ + nullable: false, + unique: true, + }) + username: string; + + @Column({ + nullable: false, + }) + fullname: string; + + @Column({ + type: 'enum', + enum: Role, + nullable: false, + default: Role.STUDENT, + }) + role: Role; + + @Column({ + nullable: false, + unique: true, + }) + password: string; + + @Column({ + nullable: false, + unique: true, + }) + email: string; + + @OneToMany(() => Course, (course) => course.teacher) + courses: Course[]; + + @CreateDateColumn({ + type: 'timestamp with time zone', + nullable: false, + }) + createdAt: Date; + + @UpdateDateColumn({ + type: 'timestamp with time zone', + nullable: false, + }) + updatedAt: Date; + + @Column({ + nullable: true, + }) + profileKey: string; } diff --git a/src/user/user.module.ts b/src/user/user.module.ts index d1a24ae..6fedadd 100644 --- a/src/user/user.module.ts +++ b/src/user/user.module.ts @@ -3,11 +3,15 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { UserController } from './user.controller'; import { User } from './user.entity'; import { UserService } from './user.service'; +import { FileModule } from 'src/file/file.module'; @Module({ - imports: [TypeOrmModule.forFeature([User])], - controllers: [UserController], - providers: [UserService], - exports: [UserService], + imports: [ + TypeOrmModule.forFeature([User]), + FileModule, + ], + controllers: [UserController], + providers: [UserService], + exports: [UserService], }) -export class UserModule {} +export class UserModule { } diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 053eefc..401f59d 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,75 +1,72 @@ import { - BadRequestException, - Inject, - Injectable, - NotFoundException, + BadRequestException, + Inject, + Injectable, + NotFoundException, } from '@nestjs/common'; -import { hash } from 'argon2'; import { createPagination } from 'src/shared/pagination'; import { FindOneOptions, ILike, Repository } from 'typeorm'; import { CreateUserDto } from './dtos/create-user.dto'; -import { UpdateUserDto } from './dtos/update-user.dto'; import { PaginatedUserResponseDto } from './dtos/user-response.dto'; import { User } from './user.entity'; +import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; @Injectable() export class UserService { - constructor( - @Inject('UserRepository') - private readonly userRepository: Repository, - ) {} + constructor( + @Inject('UserRepository') + private readonly userRepository: Repository, + ) { } - async findAll({ - page = 1, - limit = 20, - search = '', - }: { - page?: number; - limit?: number; - search?: string; - }): Promise { - const { find } = await createPagination(this.userRepository, { - page, - limit, - }); + async findAll({ + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }): Promise { + const { find } = await createPagination(this.userRepository, { + page, + limit, + }); - const users = await find({ - where: { email: ILike(`%${search}%`) }, - }).run(); + const users = await find({ + where: { email: ILike(`%${search}%`) }, + }).run(); - return users; - } + return users; + } - async findOne(options: FindOneOptions): Promise { - const user = this.userRepository.findOne(options); - if (!user) throw new NotFoundException('User not found'); - return user; - } + async findOne(options: FindOneOptions): Promise { + const user = this.userRepository.findOne(options); + if (!user) throw new NotFoundException('User not found'); + return user; + } - async create(createUserDto: CreateUserDto): Promise { - try { - return this.userRepository.save(createUserDto); - } catch (error) { - if (error instanceof Error) throw new BadRequestException(error.message); + async create(createUserDto: CreateUserDto): Promise { + try { + return this.userRepository.save(createUserDto); + } catch (error) { + if (error instanceof Error) throw new BadRequestException(error.message); + } } - } - async update(id: string, updateUserDto: UpdateUserDto): Promise { - try { - if (updateUserDto.password) - updateUserDto.password = await hash(updateUserDto.password); - await this.userRepository.update(id, updateUserDto); - return await this.findOne({ where: { id } }); - } catch (error) { - if (error instanceof Error) throw new NotFoundException('User not found'); + async update(id: string, partialEntity: QueryDeepPartialEntity): Promise { + try { + await this.userRepository.update(id, partialEntity); + return await this.findOne({ where: { id } }); + } catch (error) { + if (error instanceof Error) throw new NotFoundException('User not found'); + } } - } - async delete(id: string): Promise { - try { - await this.userRepository.delete(id); - } catch (error) { - if (error instanceof Error) throw new NotFoundException('User not found'); + async delete(id: string): Promise { + try { + await this.userRepository.delete(id); + } catch (error) { + if (error instanceof Error) throw new NotFoundException('User not found'); + } } - } } From 5e95a17fae4f910524aa91c7ed5f50f6f7aff44b Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Fri, 15 Nov 2024 19:19:47 +0700 Subject: [PATCH 036/155] feat: create exam api and dto --- src/app.module.ts | 4 +- src/course-module/course-module.entity.ts | 7 ++ src/exam/dtos/create-exam.dto.ts | 74 ++++++++++++ src/exam/dtos/exam-response.dto.ts | 117 ++++++++++++++++++ src/exam/dtos/update-exam.dto.ts | 4 + src/exam/exam.controller.ts | 140 ++++++++++++++++++++++ src/exam/exam.entity.ts | 68 +++++++++++ src/exam/exam.module.ts | 21 ++++ src/exam/exam.providers.ts | 8 ++ src/exam/exam.service.ts | 119 ++++++++++++++++++ src/shared/configs/database.config.ts | 3 +- src/shared/enums/exam-status.enum.ts | 5 + src/shared/enums/index.ts | 1 + 13 files changed, 569 insertions(+), 2 deletions(-) create mode 100644 src/exam/dtos/create-exam.dto.ts create mode 100644 src/exam/dtos/exam-response.dto.ts create mode 100644 src/exam/dtos/update-exam.dto.ts create mode 100644 src/exam/exam.controller.ts create mode 100644 src/exam/exam.entity.ts create mode 100644 src/exam/exam.module.ts create mode 100644 src/exam/exam.providers.ts create mode 100644 src/exam/exam.service.ts create mode 100644 src/shared/enums/exam-status.enum.ts diff --git a/src/app.module.ts b/src/app.module.ts index e9fd6e7..607ff4d 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -21,6 +21,7 @@ import { UserStreak } from './user-streak/user-streak.entity'; import { UserStreakModule } from './user-streak/user-streak.module'; import { User } from './user/user.entity'; import { UserModule } from './user/user.module'; +import { ExamModule } from './exam/exam.module'; const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); @@ -52,6 +53,7 @@ const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); CourseModule, CourseModuleModule, ChapterModule, + ExamModule ], controllers: [AppController], providers: [ @@ -66,4 +68,4 @@ const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); }, ], }) -export class AppModule {} +export class AppModule { } diff --git a/src/course-module/course-module.entity.ts b/src/course-module/course-module.entity.ts index ae9b273..476d454 100644 --- a/src/course-module/course-module.entity.ts +++ b/src/course-module/course-module.entity.ts @@ -1,5 +1,6 @@ import { Chapter } from 'src/chapter/chapter.entity'; import { Course } from 'src/course/course.entity'; +import { Exam } from 'src/exam/exam.entity'; import { Column, CreateDateColumn, @@ -7,6 +8,7 @@ import { JoinColumn, ManyToOne, OneToMany, + OneToOne, PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; @@ -47,6 +49,11 @@ export class CourseModule { @Column({ name: 'course_id' }) courseId: string; + @OneToOne(() => Exam, (exam) => exam.courseModule, { + cascade: true, + }) + exam: Exam; + @CreateDateColumn({ type: 'timestamp', name: 'created_at', diff --git a/src/exam/dtos/create-exam.dto.ts b/src/exam/dtos/create-exam.dto.ts new file mode 100644 index 0000000..efd75a1 --- /dev/null +++ b/src/exam/dtos/create-exam.dto.ts @@ -0,0 +1,74 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsEnum, IsInt, IsNotEmpty, IsOptional } from 'class-validator'; +import { ExamStatus } from 'src/shared/enums'; +export class CreateExamDto { + @IsNotEmpty() + @ApiProperty({ + description: 'Course Module ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + courseModuleId: string; + + @IsNotEmpty() + @ApiProperty({ + description: 'Exam title', + type: String, + example: 'Biology', + }) + title: string; + + @IsOptional() + @ApiProperty({ + description: 'Exam description', + type: String, + example: 'This course is an introduction to biology', + }) + description?: string; + + @IsNotEmpty() + @IsInt() + @ApiProperty({ + description: 'time limit to do exam.', + type: Number, + example: 20, + }) + timeLimit: number = 20; + + @IsNotEmpty() + @IsInt() + @ApiProperty({ + description: 'Score to pass exam.', + type: Number, + example: 50, + }) + passingScore: number; + + @IsNotEmpty() + @IsInt() + @ApiProperty({ + description: 'Max attempts to do exam.', + type: Number, + example: 1, + }) + maxAttempts: number; + + @IsNotEmpty() + @IsBoolean() + @ApiProperty({ + description: 'Shuffle question.', + type: Boolean, + example: false, + }) + shuffleQuestions: boolean = false; + + @IsNotEmpty() + @IsEnum(ExamStatus) + @ApiProperty({ + description: 'Exam status', + type: String, + example: ExamStatus.DRAFT, + enum: ExamStatus + }) + status: ExamStatus = ExamStatus.DRAFT; +} diff --git a/src/exam/dtos/exam-response.dto.ts b/src/exam/dtos/exam-response.dto.ts new file mode 100644 index 0000000..d02e36e --- /dev/null +++ b/src/exam/dtos/exam-response.dto.ts @@ -0,0 +1,117 @@ +import { ExamStatus } from "src/shared/enums"; +import { Exam } from "../exam.entity"; +import { ApiProperty } from "@nestjs/swagger"; +import { PaginatedResponse } from "src/shared/pagination/dtos/paginate-response.dto"; +import { CourseModuleResponseDto } from "src/course-module/dtos/course-module-response.dto"; + +export class ExamResponseDto { + @ApiProperty({ + description: 'Exam ID', + type: String, + example: '123e4567-e89b-12d3-a456-426614174000', + }) + id: string; + + @ApiProperty({ + description: 'Course Module Data', + type: String, + example: { + "id": "8d4887aa-28e7-4d0e-844c-28a8ccead003" + }, + }) + courseModuleId: string; + + @ApiProperty({ + description: 'Exam title', + type: String, + example: 'Exam title', + }) + title: string; + + @ApiProperty({ + description: 'Exam description', + type: String, + example: 'Exam description', + }) + description: string; + + @ApiProperty({ + description: 'Timelimit to do exam.', + type: Number, + example: 20, + }) + timeLimit: Number; + + @ApiProperty({ + description: 'Score to pass exam.', + type: Number, + example: 20, + }) + passingScore: Number; + + @ApiProperty({ + description: 'Max attempts to do exam.', + type: Number, + example: 1, + }) + maxAttempts: Number; + + @ApiProperty({ + description: 'Shuffle question', + type: Boolean, + example: false, + }) + shuffleQuestions: Boolean; + + @ApiProperty({ + description: 'Exam status', + type: String, + enum: ExamStatus, + example: ExamStatus.DRAFT, + }) + status: ExamStatus; + + @ApiProperty({ + description: 'Exam created date', + type: Date, + example: new Date(), + }) + createdAt: Date; + + @ApiProperty({ + description: 'Exam updated date', + type: Date, + example: new Date(), + }) + updatedAt: Date; + + constructor(exam: Exam) { + this.id = exam.id; + this.courseModuleId = exam.courseModuleId + this.title = exam.title; + this.description = exam.description; + this.timeLimit = exam.timeLimit; + this.passingScore = exam.passingScore; + this.maxAttempts = exam.maxAttempts; + this.shuffleQuestions = exam.shuffleQuestions; + this.status = exam.status; + this.createdAt = exam.createdAt; + this.updatedAt = exam.updatedAt; + } +} + +export class PaginatedExamResponseDto extends PaginatedResponse( + ExamResponseDto, +) { + constructor( + Exam: Exam[], + total: number, + pageSize: number, + currentPage: number, + ) { + const ExamDtos = Exam.map( + (exam) => new ExamResponseDto(exam), + ); + super(ExamDtos, total, pageSize, currentPage); + } +} \ No newline at end of file diff --git a/src/exam/dtos/update-exam.dto.ts b/src/exam/dtos/update-exam.dto.ts new file mode 100644 index 0000000..43c79b9 --- /dev/null +++ b/src/exam/dtos/update-exam.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateExamDto } from './create-exam.dto'; + +export class UpdateExamDto extends PartialType(CreateExamDto) { } \ No newline at end of file diff --git a/src/exam/exam.controller.ts b/src/exam/exam.controller.ts new file mode 100644 index 0000000..d454de3 --- /dev/null +++ b/src/exam/exam.controller.ts @@ -0,0 +1,140 @@ +import { + Controller, + Injectable, + Get, + Param, + ParseUUIDPipe, + HttpStatus, + Post, + Body, + Patch, + Delete, + HttpCode, + Query, + Req, +} from '@nestjs/common'; +import { ApiTags, ApiBearerAuth, ApiResponse, ApiQuery, ApiParam } from '@nestjs/swagger'; +import { ExamService } from './exam.service'; +import { ExamResponseDto, PaginatedExamResponseDto } from './dtos/exam-response.dto'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums'; +import { CreateExamDto } from './dtos/create-exam.dto'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { UpdateExamDto } from './dtos/update-exam.dto'; + +@Controller('exam') +@ApiTags('Exam') +@ApiBearerAuth() +@Injectable() +export class ExamController { + constructor(private readonly examService: ExamService) { } + + @Get() + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all exams', + type: ExamResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findAll(@Req() request: AuthenticatedRequest, @Query() query: PaginateQueryDto): Promise { + return await this.examService.findAll(request, { + page: query.page, + limit: query.limit, + search: query.search, + }); + } + + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns an exam', + type: ExamResponseDto, + }) + async findOne( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const exam = await this.examService.findOne(request, { where: { id } }); + return new ExamResponseDto(exam); + } + + @Post() + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Create an exam', + type: ExamResponseDto, + }) + async createExam(@Body() createExamDto: CreateExamDto + ): Promise { + const exam = await this.examService.createExam(createExamDto); + return new ExamResponseDto(exam); + } + + @Patch(':id') + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Update an exam', + type: ExamResponseDto, + }) + async updateExam(@Req() request: AuthenticatedRequest, @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, @Body() updateExamDto: UpdateExamDto + ): Promise { + const exam = await this.examService.updateExam(request, id, updateExamDto); + return new ExamResponseDto(exam); + } + + @Delete(':id') + @Roles(Role.TEACHER) + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Delete an exam', + }) + @HttpCode(HttpStatus.NO_CONTENT) + async deleteExam(@Req() request: AuthenticatedRequest, @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise<{ massage: string }> { + await this.examService.deleteExam(request, id); + return { massage: 'Exam deleted successfully' }; + } +} \ No newline at end of file diff --git a/src/exam/exam.entity.ts b/src/exam/exam.entity.ts new file mode 100644 index 0000000..a839a1d --- /dev/null +++ b/src/exam/exam.entity.ts @@ -0,0 +1,68 @@ +import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany, OneToOne, JoinColumn } from "typeorm"; +import { ExamStatus } from "src/shared/enums"; +import { CourseModule } from "src/course-module/course-module.entity"; + +@Entity() +export class Exam { + @PrimaryGeneratedColumn("uuid") + id: string; + + @OneToOne(() => CourseModule, (courseModule) => courseModule.exam, { + onDelete: 'CASCADE', + nullable: false, + }) + @JoinColumn({ name: 'course_module_id' }) + courseModule: CourseModule; + + @Column({ name: 'course_module_id' }) + courseModuleId: string; + + @Column({ + nullable: false, + }) + title: string; + + @Column({ + nullable: true, + }) + description: string; + + @Column({ + nullable: false, + default: 20, + }) + timeLimit: number; + + @Column({ + nullable: false, + }) + passingScore: number; + + @Column({ + nullable: false, + }) + maxAttempts: number; + + @Column({ + nullable: false, + default: false, + }) + shuffleQuestions: boolean; + + @Column({ + enum: ExamStatus, + nullable: false, + default: ExamStatus.DRAFT, + }) + status: ExamStatus; + + @CreateDateColumn({ + type: "timestamp with time zone" + }) + createdAt: Date; + + @UpdateDateColumn({ + type: "timestamp with time zone" + }) + updatedAt: Date; +} \ No newline at end of file diff --git a/src/exam/exam.module.ts b/src/exam/exam.module.ts new file mode 100644 index 0000000..2323848 --- /dev/null +++ b/src/exam/exam.module.ts @@ -0,0 +1,21 @@ +import { Module } from "@nestjs/common"; +import { DatabaseModule } from "src/database/database.module"; +import { ExamController } from "./exam.controller"; +import { ExamService } from "./exam.service"; +import { TypeOrmModule } from "@nestjs/typeorm"; +import { Exam } from "./exam.entity"; +import { examProviders } from "./exam.providers"; + +@Module({ + imports: [ + DatabaseModule, + TypeOrmModule.forFeature([Exam]), + ], + controllers: [ExamController], + providers: [ + ...examProviders, + ExamService, + ], + exports: [ExamService] +}) +export class ExamModule { } \ No newline at end of file diff --git a/src/exam/exam.providers.ts b/src/exam/exam.providers.ts new file mode 100644 index 0000000..a48d639 --- /dev/null +++ b/src/exam/exam.providers.ts @@ -0,0 +1,8 @@ +import { DataSource } from "typeorm"; +import { Exam } from "./exam.entity"; + +export const examProviders = [{ + provide: 'ExamRepository', + useFactory: (dataSource: DataSource) => dataSource.getRepository(Exam), + inject: ['DataSource'], +}]; \ No newline at end of file diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts new file mode 100644 index 0000000..d06dcbe --- /dev/null +++ b/src/exam/exam.service.ts @@ -0,0 +1,119 @@ +import { Injectable, Inject, NotFoundException } from "@nestjs/common"; +import { Repository, FindOneOptions, ILike, FindOptionsWhere, FindOptionsSelect } from "typeorm"; +import { Exam } from "./exam.entity"; +import { CreateExamDto } from "./dtos/create-exam.dto"; +import { PaginatedExamResponseDto } from "./dtos/exam-response.dto"; +import { createPagination } from "src/shared/pagination"; +import { ExamStatus } from "src/shared/enums"; +import { AuthenticatedRequest } from "src/auth/interfaces/authenticated-request.interface"; +import { isInstance } from "class-validator"; +import { string } from "joi"; +import { UpdateExamDto } from "./dtos/update-exam.dto"; + +@Injectable() +export class ExamService { + constructor( + @Inject('ExamRepository') + private readonly examRepository: Repository, + ) { } + + async findAll(request: AuthenticatedRequest, { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }): Promise { + const { find } = await createPagination(this.examRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateCondition(request, search) + const exam = await find({ + where: whereCondition, + relations: { + // Only load courseModule relation, not course or teacher + courseModule: true, + }, + }).run(); + + return exam; + } + + private validateAndCreateCondition(request: AuthenticatedRequest, search: string): FindOptionsWhere { + const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; + + if (request.user.role === 'student') { + return { ...baseSearch, status: ExamStatus.PUBLISHED }; + } + + if (request.user.role === 'teacher') { + return { + ...baseSearch, + courseModule: { + course: { + teacher: { + id: request.user.id, + } + } + } + }; + } + + if (request.user.role === 'admin') { + return { ...baseSearch }; + } + + return { ...baseSearch }; + } + + async findOne(request: AuthenticatedRequest, options: FindOneOptions = {}): Promise { + const whereCondition = this.validateAndCreateCondition(request, ''); + + const exam = await this.examRepository.findOne({ + ...options, + where: whereCondition, + relations: { + courseModule: true, + }, + }); + + if (!exam) { + throw new NotFoundException('Exam not found'); + } + + return exam; + } + + async createExam(createExamDto: CreateExamDto): Promise { + const ExamModule = this.examRepository.create(createExamDto); + await this.examRepository.save(ExamModule); + if (!ExamModule) + throw new NotFoundException("Can't create exam"); + return ExamModule; + } + + async updateExam(request: AuthenticatedRequest, id: string, updateExamDto: UpdateExamDto): Promise { + const examInData = await this.findOne(request, { where: { id } }) + if (examInData.status != ExamStatus.DRAFT && updateExamDto.status == ExamStatus.DRAFT) { + throw new NotFoundException("Can't change status to draft"); + } + const exam = await this.examRepository.update(id, updateExamDto); + if (!exam) + throw new NotFoundException("Can't update exam"); + return await this.examRepository.findOne({ where: { id } }); + } + + async deleteExam(request: AuthenticatedRequest, id: string): Promise { + try { + if (await this.findOne(request, { where: { id } })) { + await this.examRepository.delete(id); + } + } catch (error) { + if (error instanceof Error) throw new NotFoundException('Exam not found'); + } + } +} \ No newline at end of file diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index d3b2363..66bf01e 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -8,6 +8,7 @@ import { UserStreak } from 'src/user-streak/user-streak.entity'; import { User } from 'src/user/user.entity'; import { DataSource, DataSourceOptions } from 'typeorm'; import { GLOBAL_CONFIG } from '../constants/global-config.constant'; +import { Exam } from 'src/exam/exam.entity'; const configService = new ConfigService(); @@ -19,7 +20,7 @@ export const databaseConfig: DataSourceOptions = { password: configService.get(GLOBAL_CONFIG.DB_PASSWORD), database: configService.get(GLOBAL_CONFIG.DB_DATABASE), logging: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), - entities: [User, UserStreak, Category, Course, CourseModule, Chapter], + entities: [User, UserStreak, Category, Course, CourseModule, Chapter, Exam], }; export default new DataSource(databaseConfig); diff --git a/src/shared/enums/exam-status.enum.ts b/src/shared/enums/exam-status.enum.ts new file mode 100644 index 0000000..f3e8c97 --- /dev/null +++ b/src/shared/enums/exam-status.enum.ts @@ -0,0 +1,5 @@ +export enum ExamStatus { + ARCHIVED = "archived", + DRAFT = "draft", + PUBLISHED = "published", +} \ No newline at end of file diff --git a/src/shared/enums/index.ts b/src/shared/enums/index.ts index 23f3511..e4a61e3 100644 --- a/src/shared/enums/index.ts +++ b/src/shared/enums/index.ts @@ -2,3 +2,4 @@ export * from './course-level.enum'; export * from './course-status.enum'; export * from './roles.enum'; export * from './environment.enum'; +export * from './exam-status.enum' \ No newline at end of file From 9b6bc3bc2938905cc392a08bc1821104057bc72d Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Fri, 15 Nov 2024 20:17:13 +0700 Subject: [PATCH 037/155] fix: string to enum --- src/exam/exam.controller.ts | 1 + src/exam/exam.service.ts | 12 +++++------- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/exam/exam.controller.ts b/src/exam/exam.controller.ts index d454de3..2ae67bc 100644 --- a/src/exam/exam.controller.ts +++ b/src/exam/exam.controller.ts @@ -91,6 +91,7 @@ export class ExamController { description: 'Create an exam', type: ExamResponseDto, }) + @HttpCode(HttpStatus.CREATED) async createExam(@Body() createExamDto: CreateExamDto ): Promise { const exam = await this.examService.createExam(createExamDto); diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts index d06dcbe..c07f4a2 100644 --- a/src/exam/exam.service.ts +++ b/src/exam/exam.service.ts @@ -4,10 +4,8 @@ import { Exam } from "./exam.entity"; import { CreateExamDto } from "./dtos/create-exam.dto"; import { PaginatedExamResponseDto } from "./dtos/exam-response.dto"; import { createPagination } from "src/shared/pagination"; -import { ExamStatus } from "src/shared/enums"; +import { ExamStatus, Role } from "src/shared/enums"; import { AuthenticatedRequest } from "src/auth/interfaces/authenticated-request.interface"; -import { isInstance } from "class-validator"; -import { string } from "joi"; import { UpdateExamDto } from "./dtos/update-exam.dto"; @Injectable() @@ -46,11 +44,11 @@ export class ExamService { private validateAndCreateCondition(request: AuthenticatedRequest, search: string): FindOptionsWhere { const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; - if (request.user.role === 'student') { + if (request.user.role === Role.STUDENT) { return { ...baseSearch, status: ExamStatus.PUBLISHED }; } - if (request.user.role === 'teacher') { + if (request.user.role === Role.TEACHER) { return { ...baseSearch, courseModule: { @@ -63,11 +61,11 @@ export class ExamService { }; } - if (request.user.role === 'admin') { + if (request.user.role === Role.ADMIN) { return { ...baseSearch }; } - return { ...baseSearch }; + return { ...baseSearch, status: ExamStatus.PUBLISHED }; } async findOne(request: AuthenticatedRequest, options: FindOneOptions = {}): Promise { From bf11774911db1e6baa841873b06e7fff86d84104 Mon Sep 17 00:00:00 2001 From: khris-xp Date: Fri, 15 Nov 2024 20:30:21 +0700 Subject: [PATCH 038/155] feat: enrollment module service --- src/app.module.ts | 2 + src/course/course.entity.ts | 4 + src/enrollment/dtos/create-enrollment.dto.ts | 74 ++++++++++ .../dtos/enrollment-response.dto.ts | 84 ++++++++++++ src/enrollment/dtos/update-enrollment.dto.ts | 4 + src/enrollment/enrollment.controller.ts | 126 ++++++++++++++++++ src/enrollment/enrollment.entity.ts | 72 ++++++++++ src/enrollment/enrollment.module.ts | 12 ++ src/enrollment/enrollment.service.ts | 92 +++++++++++++ .../enums/enrollment-status.enum.ts | 5 + src/shared/configs/database.config.ts | 11 +- src/user/user.entity.ts | 14 +- 12 files changed, 494 insertions(+), 6 deletions(-) create mode 100644 src/enrollment/dtos/create-enrollment.dto.ts create mode 100644 src/enrollment/dtos/enrollment-response.dto.ts create mode 100644 src/enrollment/dtos/update-enrollment.dto.ts create mode 100644 src/enrollment/enrollment.controller.ts create mode 100644 src/enrollment/enrollment.entity.ts create mode 100644 src/enrollment/enrollment.module.ts create mode 100644 src/enrollment/enrollment.service.ts create mode 100644 src/enrollment/enums/enrollment-status.enum.ts diff --git a/src/app.module.ts b/src/app.module.ts index e9fd6e7..ae766b6 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -13,6 +13,7 @@ import { CourseModuleModule } from './course-module/course-module.module'; import { Course } from './course/course.entity'; import { CourseModule } from './course/course.module'; import { DatabaseModule } from './database/database.module'; +import { EnrollmentModule } from './enrollment/enrollment.module'; import { databaseConfig } from './shared/configs/database.config'; import { dotenvConfig } from './shared/configs/dotenv.config'; import { GLOBAL_CONFIG } from './shared/constants/global-config.constant'; @@ -52,6 +53,7 @@ const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); CourseModule, CourseModuleModule, ChapterModule, + EnrollmentModule, ], controllers: [AppController], providers: [ diff --git a/src/course/course.entity.ts b/src/course/course.entity.ts index 246dab9..e42f56f 100644 --- a/src/course/course.entity.ts +++ b/src/course/course.entity.ts @@ -1,5 +1,6 @@ import { Category } from 'src/category/category.entity'; import { CourseModule } from 'src/course-module/course-module.entity'; +import { Enrollment } from 'src/enrollment/enrollment.entity'; import { CourseLevel } from 'src/shared/enums/course-level.enum'; import { CourseStatus } from 'src/shared/enums/course-status.enum'; import { User } from 'src/user/user.entity'; @@ -42,6 +43,9 @@ export class Course { }) modules: CourseModule[]; + @OneToMany(() => Enrollment, (enrolment) => enrolment.course) + enrolments: Enrollment[]; + @Column({ type: String, nullable: false, diff --git a/src/enrollment/dtos/create-enrollment.dto.ts b/src/enrollment/dtos/create-enrollment.dto.ts new file mode 100644 index 0000000..c8f6d97 --- /dev/null +++ b/src/enrollment/dtos/create-enrollment.dto.ts @@ -0,0 +1,74 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + IsBoolean, + IsEnum, + IsNotEmpty, + IsNumber, + IsOptional, + IsString, + IsUUID, +} from 'class-validator'; +import { EnrollmentStatus } from '../enums/enrollment-status.enum'; + +export class CreateEnrollmentDto { + @IsNotEmpty() + @IsUUID() + @ApiProperty({ + description: 'User ID', + type: String, + format: 'uuid', + }) + userId: string; + + @IsNotEmpty() + @IsUUID() + @ApiProperty({ + description: 'Course ID', + type: String, + format: 'uuid', + }) + courseId: string; + + @IsOptional() + @IsBoolean() + @ApiProperty({ + description: 'Certificate issued status', + type: Boolean, + default: false, + }) + certificateIssued?: boolean; + + @IsOptional() + @IsNumber() + @ApiProperty({ + description: 'Completion rate', + type: Number, + default: 0, + }) + completionRate?: number; + + @IsOptional() + @IsEnum(EnrollmentStatus) + @ApiProperty({ + description: 'Enrollment status', + enum: EnrollmentStatus, + default: EnrollmentStatus.ACTIVE, + }) + status?: EnrollmentStatus; + + @IsNotEmpty() + @ApiProperty({ + description: 'Enrollment date', + type: Date, + default: new Date(), + }) + enrolledAt: Date; + + @IsOptional() + @ApiProperty({ + description: 'Completion date', + type: Date, + required: false, + }) + completedAt?: Date; +} diff --git a/src/enrollment/dtos/enrollment-response.dto.ts b/src/enrollment/dtos/enrollment-response.dto.ts new file mode 100644 index 0000000..76979d4 --- /dev/null +++ b/src/enrollment/dtos/enrollment-response.dto.ts @@ -0,0 +1,84 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { CourseResponseDto } from 'src/course/dtos'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { UserResponseDto } from 'src/user/dtos/user-response.dto'; +import { Enrollment } from '../enrollment.entity'; +import { EnrollmentStatus } from '../enums/enrollment-status.enum'; + +export class EnrollmentResponseDto { + @ApiProperty({ + description: 'Enrollment ID', + type: String, + }) + id: string; + + @ApiProperty({ + description: 'User data', + type: UserResponseDto, + }) + user: UserResponseDto; + + @ApiProperty({ + description: 'Course data', + type: CourseResponseDto, + }) + course: CourseResponseDto; + + @ApiProperty({ + description: 'Enrollment status', + enum: EnrollmentStatus, + }) + status: EnrollmentStatus; + + @ApiProperty({ + description: 'Completion rate', + type: Number, + }) + completionRate: number; + + @ApiProperty({ + description: 'Certificate issued status', + type: Boolean, + }) + certificateIssued: boolean; + + @ApiProperty({ + description: 'Enrollment date', + type: Date, + }) + enrolledAt: Date; + + @ApiProperty({ + description: 'Completion date', + type: Date, + nullable: true, + }) + completedAt: Date; + + constructor(enrollment: Enrollment) { + this.id = enrollment.id; + this.user = new UserResponseDto(enrollment.user); + this.course = new CourseResponseDto(enrollment.course); + this.status = enrollment.status; + this.completionRate = enrollment.completionRate; + this.certificateIssued = enrollment.certificateIssued; + this.enrolledAt = enrollment.enrolledAt; + this.completedAt = enrollment.completedAt; + } +} + +export class PaginatedEnrollmentResponseDto extends PaginatedResponse( + EnrollmentResponseDto, +) { + constructor( + enrollments: Enrollment[], + total: number, + pageSize: number, + currentPage: number, + ) { + const enrollmentDtos = enrollments.map( + (enrollment) => new EnrollmentResponseDto(enrollment), + ); + super(enrollmentDtos, total, pageSize, currentPage); + } +} diff --git a/src/enrollment/dtos/update-enrollment.dto.ts b/src/enrollment/dtos/update-enrollment.dto.ts new file mode 100644 index 0000000..44f2c35 --- /dev/null +++ b/src/enrollment/dtos/update-enrollment.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateEnrollmentDto } from './create-enrollment.dto'; + +export class UpdateEnrollmentDto extends PartialType(CreateEnrollmentDto) {} diff --git a/src/enrollment/enrollment.controller.ts b/src/enrollment/enrollment.controller.ts new file mode 100644 index 0000000..40ceda1 --- /dev/null +++ b/src/enrollment/enrollment.controller.ts @@ -0,0 +1,126 @@ +import { + BadRequestException, + Body, + Controller, + Delete, + Get, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, +} from '@nestjs/common'; +import { + ApiBearerAuth, + ApiParam, + ApiQuery, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { CreateEnrollmentDto } from './dtos/create-enrollment.dto'; +import { + EnrollmentResponseDto, + PaginatedEnrollmentResponseDto, +} from './dtos/enrollment-response.dto'; +import { UpdateEnrollmentDto } from './dtos/update-enrollment.dto'; +import { EnrollmentService } from './enrollment.service'; + +@Controller('enrollment') +@ApiTags('Enrollment') +@ApiBearerAuth() +@Injectable() +export class EnrollmentController { + constructor(private readonly enrollmentService: EnrollmentService) {} + + @Get() + @ApiResponse({ + status: HttpStatus.OK, + type: EnrollmentResponseDto, + description: 'Get all enrollments', + isArray: true, + }) + async findAll( + @Query() query: PaginateQueryDto, + ): Promise { + return this.enrollmentService.findAll(query); + } + + @Get(':id') + @ApiParam({ + name: 'id', + type: String, + description: 'Enrollment ID', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: EnrollmentResponseDto, + description: 'Get enrollment by ID', + }) + async findOne( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + return this.enrollmentService.findOne(id, { where: { id } }); + } + + @Post() + @Roles(Role.STUDENT) + @ApiResponse({ + status: HttpStatus.CREATED, + type: EnrollmentResponseDto, + description: 'Create enrollment', + }) + async create( + @Body() createEnrollmentDto: CreateEnrollmentDto, + @Req() req: AuthenticatedRequest, + ): Promise { + return this.enrollmentService.create(createEnrollmentDto); + } + + @Patch(':id') + @Roles(Role.STUDENT) + @ApiParam({ + name: 'id', + type: String, + description: 'Enrollment ID', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: EnrollmentResponseDto, + description: 'Update enrollment by ID', + }) + async update( + @Param('id', ParseUUIDPipe) id: string, + @Body() updateEnrollmentDto: UpdateEnrollmentDto, + ): Promise { + return this.enrollmentService.update(id, updateEnrollmentDto); + } + + @Delete(':id') + @Roles(Role.STUDENT) + @ApiParam({ + name: 'id', + type: String, + description: 'Enrollment ID', + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Delete enrollment by ID', + }) + async remove(@Param('id', ParseUUIDPipe) id: string): Promise { + return this.enrollmentService.remove(id); + } +} diff --git a/src/enrollment/enrollment.entity.ts b/src/enrollment/enrollment.entity.ts new file mode 100644 index 0000000..d3479d3 --- /dev/null +++ b/src/enrollment/enrollment.entity.ts @@ -0,0 +1,72 @@ +import { Course } from 'src/course/course.entity'; +import { User } from 'src/user/user.entity'; +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; +import { EnrollmentStatus } from './enums/enrollment-status.enum'; + +@Entity() +export class Enrollment { + @PrimaryGeneratedColumn('uuid') + id: string; + + @ManyToOne(() => User, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'user_id' }) + user: User; + + @ManyToOne(() => Course, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'course_id' }) + course: Course; + + @Column({ + type: 'enum', + enum: EnrollmentStatus, + default: EnrollmentStatus.ACTIVE, + }) + status: EnrollmentStatus; + + @Column({ + type: 'decimal', + precision: 5, + scale: 2, + default: 0, + }) + completionRate: number; + + @Column({ + type: 'boolean', + default: false, + }) + certificateIssued: boolean; + + @Column({ + type: 'timestamp', + name: 'enrolled_at', + }) + enrolledAt: Date; + + @Column({ + type: 'timestamp', + name: 'completed_at', + nullable: true, + }) + completedAt: Date; + + @CreateDateColumn({ + type: 'timestamp', + name: 'created_at', + }) + createdAt: Date; + + @UpdateDateColumn({ + type: 'timestamp', + name: 'updated_at', + }) + updatedAt: Date; +} diff --git a/src/enrollment/enrollment.module.ts b/src/enrollment/enrollment.module.ts new file mode 100644 index 0000000..e6e7746 --- /dev/null +++ b/src/enrollment/enrollment.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { EnrollmentController } from './enrollment.controller'; +import { Enrollment } from './enrollment.entity'; +import { EnrollmentService } from './enrollment.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Enrollment])], + controllers: [EnrollmentController], + providers: [EnrollmentService], +}) +export class EnrollmentModule {} diff --git a/src/enrollment/enrollment.service.ts b/src/enrollment/enrollment.service.ts new file mode 100644 index 0000000..22fd237 --- /dev/null +++ b/src/enrollment/enrollment.service.ts @@ -0,0 +1,92 @@ +import { + BadRequestException, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { createPagination } from 'src/shared/pagination'; +import { FindOneOptions, FindOptionsWhere, ILike, Repository } from 'typeorm'; +import { CreateEnrollmentDto } from './dtos/create-enrollment.dto'; +import { PaginatedEnrollmentResponseDto } from './dtos/enrollment-response.dto'; +import { UpdateEnrollmentDto } from './dtos/update-enrollment.dto'; +import { Enrollment } from './enrollment.entity'; +import { EnrollmentStatus } from './enums/enrollment-status.enum'; + +@Injectable() +export class EnrollmentService { + constructor( + @InjectRepository(Enrollment) + private readonly enrollmentRepository: Repository, + ) {} + + async findAll({ + page = 1, + limit = 20, + }: { + page?: number; + limit?: number; + }): Promise { + const { find } = await createPagination(this.enrollmentRepository, { + page, + limit, + }); + + const enrollments = await find({ + relations: { + user: true, + course: true, + }, + }).run(); + + return enrollments; + } + + async findOne( + id: string, + options: FindOneOptions, + ): Promise { + const baseWhere = options.where as FindOptionsWhere; + const whereCondition = { ...baseWhere, id }; + + const enrollment = await this.enrollmentRepository.findOne({ + where: whereCondition, + relations: { + user: true, + course: true, + }, + }); + + if (!enrollment) { + throw new NotFoundException('Enrollment not found'); + } + + return enrollment; + } + + async create(createEnrollmentDto: CreateEnrollmentDto): Promise { + const enrollment = this.enrollmentRepository.create(createEnrollmentDto); + await this.enrollmentRepository.save(enrollment); + + return enrollment; + } + + async update( + id: string, + updateEnrollmentDto: UpdateEnrollmentDto, + ): Promise { + const enrollment = await this.findOne(id, { + where: { status: EnrollmentStatus.ACTIVE }, + }); + this.enrollmentRepository.merge(enrollment, updateEnrollmentDto); + await this.enrollmentRepository.save(enrollment); + + return enrollment; + } + + async remove(id: string): Promise { + const enrollment = await this.findOne(id, { + where: { id }, + }); + await this.enrollmentRepository.remove(enrollment); + } +} diff --git a/src/enrollment/enums/enrollment-status.enum.ts b/src/enrollment/enums/enrollment-status.enum.ts new file mode 100644 index 0000000..07420c8 --- /dev/null +++ b/src/enrollment/enums/enrollment-status.enum.ts @@ -0,0 +1,5 @@ +export enum EnrollmentStatus { + ACTIVE = 'active', + COMPLETED = 'completed', + DROPPED = 'dropped', +} diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index d3b2363..03a8874 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -4,6 +4,7 @@ import { Category } from 'src/category/category.entity'; import { Chapter } from 'src/chapter/chapter.entity'; import { CourseModule } from 'src/course-module/course-module.entity'; import { Course } from 'src/course/course.entity'; +import { Enrollment } from 'src/enrollment/enrollment.entity'; import { UserStreak } from 'src/user-streak/user-streak.entity'; import { User } from 'src/user/user.entity'; import { DataSource, DataSourceOptions } from 'typeorm'; @@ -19,7 +20,15 @@ export const databaseConfig: DataSourceOptions = { password: configService.get(GLOBAL_CONFIG.DB_PASSWORD), database: configService.get(GLOBAL_CONFIG.DB_DATABASE), logging: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), - entities: [User, UserStreak, Category, Course, CourseModule, Chapter], + entities: [ + User, + UserStreak, + Category, + Course, + CourseModule, + Chapter, + Enrollment, + ], }; export default new DataSource(databaseConfig); diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index 5faf545..c86475e 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -1,13 +1,14 @@ -import { Entity, OneToMany } from 'typeorm'; +import { Course } from 'src/course/course.entity'; +import { Enrollment } from 'src/enrollment/enrollment.entity'; +import { Role } from 'src/shared/enums/roles.enum'; import { Column, - PrimaryGeneratedColumn, CreateDateColumn, + Entity, + OneToMany, + PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; -import { Role } from 'src/shared/enums/roles.enum'; -import { Course } from 'src/course/course.entity'; - @Entity() export class User { @PrimaryGeneratedColumn('uuid') @@ -47,6 +48,9 @@ export class User { @OneToMany(() => Course, (course) => course.teacher) courses: Course[]; + @OneToMany(() => Enrollment, (enrollment) => enrollment.user) + enrollments: Enrollment[]; + @CreateDateColumn({ type: 'timestamp with time zone', nullable: false, From 501e2c7466c8c7ee370678311768f00ecb0b0133 Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Fri, 15 Nov 2024 22:57:37 +0700 Subject: [PATCH 039/155] refactor: remove CourseOwnershipGuard from AppModule and improve module configurations across course and chapter modules --- src/app.module.ts | 5 ----- src/chapter/chapter.module.ts | 12 ++++++++---- src/course-module/course-module.controller.ts | 5 ++--- src/course-module/course-module.module.ts | 3 ++- src/course/course.module.ts | 13 ++++++++++--- src/course/course.service.ts | 1 - 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/app.module.ts b/src/app.module.ts index 586f42d..2a063a5 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -20,7 +20,6 @@ import { UserStreak } from './user-streak/user-streak.entity'; import { UserStreakModule } from './user-streak/user-streak.module'; import { User } from './user/user.entity'; import { UserModule } from './user/user.module'; -import { CourseOwnershipGuard } from './shared/guards/course-ownership.guard'; import { Course } from './course/course.entity'; import { CourseModule as CourseModuleEntity } from './course-module/course-module.entity'; import { Chapter } from './chapter/chapter.entity'; @@ -73,10 +72,6 @@ const forFeatures = TypeOrmModule.forFeature([ provide: APP_GUARD, useClass: RolesGuard, }, - { - provide: APP_GUARD, - useClass: CourseOwnershipGuard, - } ], }) export class AppModule {} \ No newline at end of file diff --git a/src/chapter/chapter.module.ts b/src/chapter/chapter.module.ts index 6585cf3..326f7af 100644 --- a/src/chapter/chapter.module.ts +++ b/src/chapter/chapter.module.ts @@ -5,11 +5,15 @@ import { Chapter } from './chapter.entity'; import { chapterProviders } from './chapter.provider'; import { ChapterService } from './chapter.service'; import { CourseModuleModule } from 'src/course-module/course-module.module'; - +import { CourseOwnershipGuard } from 'src/shared/guards/course-ownership.guard'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Course } from 'src/course/course.entity'; +import { CourseModule as CourseModuleEntity } from 'src/course-module/course-module.entity'; @Module({ - imports: [DatabaseModule, CourseModuleModule], + imports: [DatabaseModule, CourseModuleModule, TypeOrmModule.forFeature([Chapter, Course, CourseModuleEntity])], controllers: [ChapterController], - providers: [...chapterProviders, ChapterService], + providers: [...chapterProviders, ChapterService, CourseOwnershipGuard, + ], exports: [ChapterService], }) -export class ChapterModule {} +export class ChapterModule { } diff --git a/src/course-module/course-module.controller.ts b/src/course-module/course-module.controller.ts index ed63ff3..e81a8d9 100644 --- a/src/course-module/course-module.controller.ts +++ b/src/course-module/course-module.controller.ts @@ -12,6 +12,7 @@ import { Post, Query, Req, + UseGuards, } from '@nestjs/common'; import { ApiBearerAuth, @@ -21,7 +22,6 @@ import { ApiTags, } from '@nestjs/swagger'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; -import { Roles } from 'src/shared/decorators/role.decorator'; import { Role } from 'src/shared/enums'; import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { CourseModuleService } from './course-module.service'; @@ -32,8 +32,8 @@ import { import { CreateCourseModuleDto } from './dtos/create-course-module.dto'; import { UpdateCourseModuleDto } from './dtos/update-course-module.dto'; import { CourseOwnership } from 'src/shared/decorators/course-ownership.decorator'; -import { Course } from 'src/course/course.entity'; import { CourseService } from 'src/course/course.service'; +import { CourseOwnershipGuard } from 'src/shared/guards/course-ownership.guard'; @Controller('course-module') @ApiTags('Course Modules') @@ -157,7 +157,6 @@ export class CourseModuleController { } @Delete(':id') - @CourseOwnership() @ApiParam({ name: 'id', type: String, diff --git a/src/course-module/course-module.module.ts b/src/course-module/course-module.module.ts index be25356..96073fe 100644 --- a/src/course-module/course-module.module.ts +++ b/src/course-module/course-module.module.ts @@ -8,7 +8,8 @@ import { CourseModuleService } from './course-module.service'; @Module({ imports: [DatabaseModule, CourseModule], controllers: [CourseModuleController], - providers: [...courseModuleProviders, CourseModuleService], + providers: [...courseModuleProviders, CourseModuleService + ], exports: [CourseModuleService], }) export class CourseModuleModule {} diff --git a/src/course/course.module.ts b/src/course/course.module.ts index 55a16b1..eeb5ac1 100644 --- a/src/course/course.module.ts +++ b/src/course/course.module.ts @@ -4,11 +4,18 @@ import { DatabaseModule } from 'src/database/database.module'; import { CourseController } from './course.controller'; import { courseProviders } from './course.provider'; import { CourseService } from './course.service'; +import { CourseOwnershipGuard } from 'src/shared/guards/course-ownership.guard'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Course } from './course.entity'; +import { Chapter } from 'src/chapter/chapter.entity'; +import { CourseModule as CourseModuleEntity } from '../course-module/course-module.entity'; @Module({ - imports: [DatabaseModule, CategoryModule], + imports: [DatabaseModule, TypeOrmModule.forFeature([CourseModuleEntity, Course, Chapter]), CategoryModule], controllers: [CourseController], - providers: [...courseProviders, CourseService], + providers: [...courseProviders, CourseService,CourseOwnershipGuard + + ], exports: [CourseService], }) -export class CourseModule {} +export class CourseModule { } diff --git a/src/course/course.service.ts b/src/course/course.service.ts index 8a1ee95..efa99da 100644 --- a/src/course/course.service.ts +++ b/src/course/course.service.ts @@ -1,6 +1,5 @@ import { BadRequestException, - ForbiddenException, Inject, Injectable, NotFoundException, From d158a1e569a721d1eca3cf5ddff6a430ff812cb9 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Fri, 15 Nov 2024 23:32:39 +0700 Subject: [PATCH 040/155] feat: enable auto loading of entities in TypeORM configuration --- src/app.module.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app.module.ts b/src/app.module.ts index 7353383..c022a91 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -39,6 +39,7 @@ const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); migrations: ['dist/database/migrations/*.js'], migrationsRun: true, synchronize: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), + autoLoadEntities: true, }), inject: [ConfigService], }), From 89ccc74d91918d5aeb09b5283c5d35492516b47c Mon Sep 17 00:00:00 2001 From: khris-xp Date: Sat, 16 Nov 2024 11:32:13 +0700 Subject: [PATCH 041/155] fixed: type in enrollment course entity --- src/course/course.entity.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/course/course.entity.ts b/src/course/course.entity.ts index e42f56f..2b0d72a 100644 --- a/src/course/course.entity.ts +++ b/src/course/course.entity.ts @@ -43,8 +43,8 @@ export class Course { }) modules: CourseModule[]; - @OneToMany(() => Enrollment, (enrolment) => enrolment.course) - enrolments: Enrollment[]; + @OneToMany(() => Enrollment, (enrollment) => enrollment.course) + enrollments: Enrollment[]; @Column({ type: String, From 470903bce4edc0d36cc033369aa1881643a182a6 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Sat, 16 Nov 2024 14:11:57 +0700 Subject: [PATCH 042/155] feat: add chat room module with service and controller; refactor exam entity and response DTO --- src/app.module.ts | 93 +++++++-------- src/category/category.module.ts | 15 ++- src/chapter/chapter.entity.ts | 135 +++++++++++----------- src/chapter/chapter.module.ts | 14 ++- src/chat-room/chat-room.controller.ts | 7 ++ src/chat-room/chat-room.entity.ts | 43 +++++++ src/chat-room/chat-room.module.ts | 13 +++ src/chat-room/chat-room.service.ts | 6 + src/course-module/course-module.module.ts | 15 ++- src/course/course.module.ts | 8 +- src/exam/dtos/exam-response.dto.ts | 2 +- src/exam/exam.entity.ts | 3 - src/exam/exam.service.ts | 2 +- src/shared/configs/database.config.ts | 18 --- src/shared/pagination/typeorm.ts | 94 +++++++-------- src/user-streak/user-streak.module.ts | 7 +- src/user/user.entity.ts | 27 +---- 17 files changed, 272 insertions(+), 230 deletions(-) create mode 100644 src/chat-room/chat-room.controller.ts create mode 100644 src/chat-room/chat-room.entity.ts create mode 100644 src/chat-room/chat-room.module.ts create mode 100644 src/chat-room/chat-room.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index 2e41704..7f57ce4 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -19,57 +19,52 @@ import { databaseConfig } from './shared/configs/database.config'; import { dotenvConfig } from './shared/configs/dotenv.config'; import { GLOBAL_CONFIG } from './shared/constants/global-config.constant'; import { RolesGuard } from './shared/guards/role.guard'; -import { UserStreak } from './user-streak/user-streak.entity'; import { UserStreakModule } from './user-streak/user-streak.module'; -import { User } from './user/user.entity'; import { UserModule } from './user/user.module'; -const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); - @Module({ - imports: [ - forFeatures, - AuthModule, - ConfigModule.forRoot({ - isGlobal: true, - validationSchema: dotenvConfig, - }), - TypeOrmModule.forRootAsync({ - imports: [ConfigModule], - useFactory: (configService: ConfigService) => ({ - ...databaseConfig, - migrations: ['dist/database/migrations/*.js'], - migrationsRun: true, - synchronize: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), - autoLoadEntities: true, - }), - inject: [ConfigService], - }), - JwtModule.register({ - global: true, - }), - DatabaseModule, - UserModule, - UserStreakModule, - CategoryModule, - CourseModule, - CourseModuleModule, - ChapterModule, - FileModule, - ExamModule, - EnrollmentModule, - ], - controllers: [AppController], - providers: [ - AppService, - { - provide: APP_GUARD, - useClass: AuthGuard, - }, - { - provide: APP_GUARD, - useClass: RolesGuard, - }, - ], + imports: [ + AuthModule, + ConfigModule.forRoot({ + isGlobal: true, + validationSchema: dotenvConfig, + }), + TypeOrmModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ + ...databaseConfig, + migrations: ['dist/database/migrations/*.js'], + migrationsRun: true, + synchronize: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), + autoLoadEntities: true, + }), + inject: [ConfigService], + }), + JwtModule.register({ + global: true, + }), + DatabaseModule, + UserModule, + UserStreakModule, + CategoryModule, + CourseModule, + CourseModuleModule, + ChapterModule, + FileModule, + ExamModule, + EnrollmentModule, + ], + controllers: [AppController], + providers: [ + AppService, + { + provide: APP_GUARD, + useClass: AuthGuard, + }, + { + provide: APP_GUARD, + useClass: RolesGuard, + }, + ], }) -export class AppModule {} +export class AppModule { } diff --git a/src/category/category.module.ts b/src/category/category.module.ts index f52011d..9f041c1 100644 --- a/src/category/category.module.ts +++ b/src/category/category.module.ts @@ -3,11 +3,16 @@ import { DatabaseModule } from 'src/database/database.module'; import { CategoryController } from './category.controller'; import { CategoryService } from './category.service'; import { categoryProviders } from './category.providers'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Category } from './category.entity'; @Module({ - imports: [DatabaseModule], - controllers: [CategoryController], - providers: [...categoryProviders, CategoryService], - exports: [CategoryService], + imports: [ + DatabaseModule, + TypeOrmModule.forFeature([Category]), + ], + controllers: [CategoryController], + providers: [...categoryProviders, CategoryService], + exports: [CategoryService], }) -export class CategoryModule {} +export class CategoryModule { } diff --git a/src/chapter/chapter.entity.ts b/src/chapter/chapter.entity.ts index 6d6e5d4..85c64b2 100644 --- a/src/chapter/chapter.entity.ts +++ b/src/chapter/chapter.entity.ts @@ -1,87 +1,84 @@ import { CourseModule } from 'src/course-module/course-module.entity'; import { - Column, - CreateDateColumn, - Entity, - JoinColumn, - ManyToOne, - PrimaryGeneratedColumn, - UpdateDateColumn, + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, } from 'typeorm'; @Entity() export class Chapter { - @PrimaryGeneratedColumn('uuid') - id: string; + @PrimaryGeneratedColumn('uuid') + id: string; - @Column({ - type: String, - nullable: false, - }) - title: string; + @Column({ + type: String, + nullable: false, + }) + title: string; - @Column({ - type: String, - nullable: false, - }) - description: string; + @Column({ + type: String, + nullable: false, + }) + description: string; - @ManyToOne(() => CourseModule, (module) => module.chapters, { - onDelete: 'CASCADE', - nullable: false, - }) - @JoinColumn({ name: 'module_id' }) - module: CourseModule; + @ManyToOne(() => CourseModule, (module) => module.chapters, { + onDelete: 'CASCADE', + nullable: false, + }) + @JoinColumn() + module: CourseModule; - @Column({ name: 'module_id' }) - moduleId: string; + @Column({ + type: String, + nullable: false, + }) + videoUrl: string; - @Column({ - type: String, - nullable: false, - }) - videoUrl: string; + @Column({ + type: String, + nullable: false, + }) + content: string; - @Column({ - type: String, - nullable: false, - }) - content: string; + @Column({ + type: String, + nullable: false, + }) + summary: string; - @Column({ - type: String, - nullable: false, - }) - summary: string; + @Column({ + type: Number, + nullable: false, + }) + duration: number; - @Column({ - type: Number, - nullable: false, - }) - duration: number; + @Column({ + type: Number, + nullable: false, + }) + orderIndex: number; - @Column({ - type: Number, - nullable: false, - }) - orderIndex: number; + @Column({ + type: Boolean, + nullable: false, + default: true, + }) + isPreview: boolean; - @Column({ - type: Boolean, - nullable: false, - default: true, - }) - isPreview: boolean; + @CreateDateColumn({ + type: 'timestamp', + name: 'created_at', + }) + createdAt: Date; - @CreateDateColumn({ - type: 'timestamp', - name: 'created_at', - }) - createdAt: Date; - - @UpdateDateColumn({ - type: 'timestamp', - name: 'updated_at', - }) - updatedAt: Date; + @UpdateDateColumn({ + type: 'timestamp', + name: 'updated_at', + }) + updatedAt: Date; } diff --git a/src/chapter/chapter.module.ts b/src/chapter/chapter.module.ts index bfdb840..8386fa3 100644 --- a/src/chapter/chapter.module.ts +++ b/src/chapter/chapter.module.ts @@ -4,11 +4,15 @@ import { ChapterController } from './chapter.controller'; import { Chapter } from './chapter.entity'; import { chapterProviders } from './chapter.provider'; import { ChapterService } from './chapter.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ - imports: [DatabaseModule], - controllers: [ChapterController], - providers: [...chapterProviders, ChapterService], - exports: [ChapterService], + imports: [ + DatabaseModule, + TypeOrmModule.forFeature([Chapter]), + ], + controllers: [ChapterController], + providers: [...chapterProviders, ChapterService], + exports: [ChapterService], }) -export class ChapterModule {} +export class ChapterModule { } diff --git a/src/chat-room/chat-room.controller.ts b/src/chat-room/chat-room.controller.ts new file mode 100644 index 0000000..46165bc --- /dev/null +++ b/src/chat-room/chat-room.controller.ts @@ -0,0 +1,7 @@ +import { Controller, Injectable } from "@nestjs/common"; + +@Controller('chat-room') +@Injectable() +export class ChatRoomController { + constructor() { } +} \ No newline at end of file diff --git a/src/chat-room/chat-room.entity.ts b/src/chat-room/chat-room.entity.ts new file mode 100644 index 0000000..4872fd1 --- /dev/null +++ b/src/chat-room/chat-room.entity.ts @@ -0,0 +1,43 @@ +import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToOne } from "typeorm"; +import { Chapter } from "src/chapter/chapter.entity"; + +@Entity() +export class ChatRoom { + @PrimaryGeneratedColumn("uuid") + id: number; + + @OneToOne(() => Chapter, { onDelete: "CASCADE" }) + chapter: Chapter; + + @Column({ + nullable: false, + }) + title: string; + + @Column({ + nullable: false, + }) + type: string; + + @Column({ + nullable: false, + }) + status: string; + + @CreateDateColumn({ + type: "timestamp", + }) + createdAt: Date; + + @Column({ + nullable: false, + default: 0, + type: "int", + }) + participantCount: number; + + @UpdateDateColumn({ + type: "timestamp", + }) + updatedAt: Date; +} \ No newline at end of file diff --git a/src/chat-room/chat-room.module.ts b/src/chat-room/chat-room.module.ts new file mode 100644 index 0000000..3e91c20 --- /dev/null +++ b/src/chat-room/chat-room.module.ts @@ -0,0 +1,13 @@ +import { Module } from "@nestjs/common"; +import { DatabaseModule } from "src/database/database.module"; +import { ChatRoomController } from "./chat-room.controller"; +import { ChatRoomService } from "./chat-room.service"; + +@Module({ + imports: [ + DatabaseModule, + ], + controllers: [ChatRoomController], + providers: [ChatRoomService], +}) +export class ChatRoomModule { } \ No newline at end of file diff --git a/src/chat-room/chat-room.service.ts b/src/chat-room/chat-room.service.ts new file mode 100644 index 0000000..f73d7bb --- /dev/null +++ b/src/chat-room/chat-room.service.ts @@ -0,0 +1,6 @@ +import { Injectable } from "@nestjs/common"; + +@Injectable() +export class ChatRoomService { + constructor() { } +} \ No newline at end of file diff --git a/src/course-module/course-module.module.ts b/src/course-module/course-module.module.ts index be25356..62edf4c 100644 --- a/src/course-module/course-module.module.ts +++ b/src/course-module/course-module.module.ts @@ -4,11 +4,16 @@ import { DatabaseModule } from 'src/database/database.module'; import { CourseModuleController } from './course-module.controller'; import { courseModuleProviders } from './course-module.provider'; import { CourseModuleService } from './course-module.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ - imports: [DatabaseModule, CourseModule], - controllers: [CourseModuleController], - providers: [...courseModuleProviders, CourseModuleService], - exports: [CourseModuleService], + imports: [ + DatabaseModule, + CourseModule, + TypeOrmModule.forFeature([CourseModule]), + ], + controllers: [CourseModuleController], + providers: [...courseModuleProviders, CourseModuleService], + exports: [CourseModuleService], }) -export class CourseModuleModule {} +export class CourseModuleModule { } diff --git a/src/course/course.module.ts b/src/course/course.module.ts index 55a16b1..c284a51 100644 --- a/src/course/course.module.ts +++ b/src/course/course.module.ts @@ -4,9 +4,15 @@ import { DatabaseModule } from 'src/database/database.module'; import { CourseController } from './course.controller'; import { courseProviders } from './course.provider'; import { CourseService } from './course.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Course } from './course.entity'; @Module({ - imports: [DatabaseModule, CategoryModule], + imports: [ + DatabaseModule, + CategoryModule, + TypeOrmModule.forFeature([Course]), +], controllers: [CourseController], providers: [...courseProviders, CourseService], exports: [CourseService], diff --git a/src/exam/dtos/exam-response.dto.ts b/src/exam/dtos/exam-response.dto.ts index d02e36e..d955ed0 100644 --- a/src/exam/dtos/exam-response.dto.ts +++ b/src/exam/dtos/exam-response.dto.ts @@ -87,7 +87,7 @@ export class ExamResponseDto { constructor(exam: Exam) { this.id = exam.id; - this.courseModuleId = exam.courseModuleId + this.courseModuleId = exam.courseModule.id; this.title = exam.title; this.description = exam.description; this.timeLimit = exam.timeLimit; diff --git a/src/exam/exam.entity.ts b/src/exam/exam.entity.ts index a839a1d..61b84db 100644 --- a/src/exam/exam.entity.ts +++ b/src/exam/exam.entity.ts @@ -14,9 +14,6 @@ export class Exam { @JoinColumn({ name: 'course_module_id' }) courseModule: CourseModule; - @Column({ name: 'course_module_id' }) - courseModuleId: string; - @Column({ nullable: false, }) diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts index c07f4a2..4fd60da 100644 --- a/src/exam/exam.service.ts +++ b/src/exam/exam.service.ts @@ -38,7 +38,7 @@ export class ExamService { }, }).run(); - return exam; + return new PaginatedExamResponseDto(exam.data, exam.meta.total, exam.meta.pageSize, exam.meta.currentPage); } private validateAndCreateCondition(request: AuthenticatedRequest, search: string): FindOptionsWhere { diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index 0fa4583..614c393 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -1,15 +1,7 @@ import { ConfigService } from '@nestjs/config'; import 'dotenv/config'; -import { Category } from 'src/category/category.entity'; -import { Chapter } from 'src/chapter/chapter.entity'; -import { CourseModule } from 'src/course-module/course-module.entity'; -import { Course } from 'src/course/course.entity'; -import { Enrollment } from 'src/enrollment/enrollment.entity'; -import { UserStreak } from 'src/user-streak/user-streak.entity'; -import { User } from 'src/user/user.entity'; import { DataSource, DataSourceOptions } from 'typeorm'; import { GLOBAL_CONFIG } from '../constants/global-config.constant'; -import { Exam } from 'src/exam/exam.entity'; const configService = new ConfigService(); @@ -21,16 +13,6 @@ export const databaseConfig: DataSourceOptions = { password: configService.get(GLOBAL_CONFIG.DB_PASSWORD), database: configService.get(GLOBAL_CONFIG.DB_DATABASE), logging: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), - entities: [ - User, - UserStreak, - Category, - Course, - CourseModule, - Chapter, - Enrollment, - Exam - ], }; export default new DataSource(databaseConfig); diff --git a/src/shared/pagination/typeorm.ts b/src/shared/pagination/typeorm.ts index 5276303..b76fec4 100644 --- a/src/shared/pagination/typeorm.ts +++ b/src/shared/pagination/typeorm.ts @@ -1,61 +1,61 @@ import { BaseEntity, FindManyOptions, Repository } from 'typeorm'; class TypeORMPagination { - constructor( - private readonly repository: Repository, - private readonly queryOptions: FindManyOptions, - private readonly page: number, - private readonly limit: number, - ) {} + constructor( + private readonly repository: Repository, + private readonly queryOptions: FindManyOptions, + private readonly page: number, + private readonly limit: number, + ) { } - protected format(data: [T[], number]) { - const [result, total] = data; - const lastPage = Math.ceil(total / this.limit); - const nextPage = this.page + 1 > lastPage ? null : this.page + 1; - const prevPage = this.page - 1 < 1 ? null : this.page - 1; + protected format(data: [T[], number]) { + const [result, total] = data; + const lastPage = Math.ceil(total / this.limit); + const nextPage = this.page + 1 > lastPage ? null : this.page + 1; + const prevPage = this.page - 1 < 1 ? null : this.page - 1; - const meta = { - total, - pageSize: Math.min(this.limit, total), - currentPage: this.page, - nextPage: nextPage, - prevPage: prevPage, - lastPage: lastPage, - }; + const meta = { + total, + pageSize: Math.min(this.limit, total), + currentPage: this.page, + nextPage: nextPage, + prevPage: prevPage, + lastPage: lastPage, + }; - return { - data: result, - meta, - }; - } + return { + data: result, + meta, + }; + } - async run() { - const data = await this.repository.findAndCount(this.queryOptions); - return this.format(data); - } + async run() { + const data = await this.repository.findAndCount(this.queryOptions); + return this.format(data); + } } type Options = { - readonly page?: number; - readonly limit?: number; + readonly page?: number; + readonly limit?: number; }; export async function createPagination( - repository: Repository, - { page = 1, limit = 20 }: Options, + repository: Repository, + { page = 1, limit = 20 }: Options, ) { - const _take = limit || 10; - const _page = page || 1; - const _skip = (_page - 1) * _take; + const _take = limit || 10; + const _page = page || 1; + const _skip = (_page - 1) * _take; - const find = (options: FindManyOptions) => { - return new TypeORMPagination( - repository, - Object.assign(options, { - take: _take, - skip: _skip, - }), - _page, - _take, - ); - }; - return { find }; + const find = (options: FindManyOptions) => { + return new TypeORMPagination( + repository, + Object.assign(options, { + take: _take, + skip: _skip, + }), + _page, + _take, + ); + }; + return { find }; } diff --git a/src/user-streak/user-streak.module.ts b/src/user-streak/user-streak.module.ts index c650bb3..d906409 100644 --- a/src/user-streak/user-streak.module.ts +++ b/src/user-streak/user-streak.module.ts @@ -3,9 +3,14 @@ import { DatabaseModule } from 'src/database/database.module'; import { UserStreakController } from './user-streak.controller'; import { userStreakProviders } from './user-streak.providers'; import { UserStreakService } from './user-streak.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { UserStreak } from './user-streak.entity'; @Module({ - imports: [DatabaseModule], + imports: [ + DatabaseModule, + TypeOrmModule.forFeature([UserStreak]), +], controllers: [UserStreakController], providers: [...userStreakProviders, UserStreakService], exports: [UserStreakService], diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index ca3f898..9baf75f 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -1,21 +1,12 @@ -import { Course } from 'src/course/course.entity'; -import { Enrollment } from 'src/enrollment/enrollment.entity'; import { Role } from 'src/shared/enums/roles.enum'; import { -<<<<<<< HEAD - Column, - CreateDateColumn, - Entity, - OneToMany, - PrimaryGeneratedColumn, - UpdateDateColumn, -======= + Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, ->>>>>>> 95ec4da3bae6ba83f175becb60440f6d278b863e } from 'typeorm'; + @Entity() export class User { @PrimaryGeneratedColumn('uuid') @@ -52,25 +43,11 @@ export class User { }) email: string; - @OneToMany(() => Course, (course) => course.teacher) - courses: Course[]; - -<<<<<<< HEAD - @OneToMany(() => Enrollment, (enrollment) => enrollment.user) - enrollments: Enrollment[]; - - @CreateDateColumn({ - type: 'timestamp with time zone', - nullable: false, - }) - createdAt: Date; -======= @CreateDateColumn({ type: 'timestamp with time zone', nullable: false, }) createdAt: Date; ->>>>>>> 95ec4da3bae6ba83f175becb60440f6d278b863e @UpdateDateColumn({ type: 'timestamp with time zone', From 11c6079bc1dd9311026a79cdcc25974b32c20f11 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sat, 16 Nov 2024 14:27:10 +0700 Subject: [PATCH 043/155] feat: create question dto --- src/question/dtos/create-question.dto.ts | 47 ++++ src/question/dtos/question-response.dto.ts | 102 ++++++++ src/question/dtos/update-question.dto.ts | 4 + src/question/question.controller.ts | 204 ++++++++++++++++ src/question/question.entity.ts | 50 ++++ src/question/question.module.ts | 22 ++ src/question/question.providers.ts | 8 + src/question/question.service.ts | 263 +++++++++++++++++++++ src/shared/configs/database.config.ts | 3 +- 9 files changed, 702 insertions(+), 1 deletion(-) create mode 100644 src/question/dtos/create-question.dto.ts create mode 100644 src/question/dtos/question-response.dto.ts create mode 100644 src/question/dtos/update-question.dto.ts create mode 100644 src/question/question.controller.ts create mode 100644 src/question/question.entity.ts create mode 100644 src/question/question.module.ts create mode 100644 src/question/question.providers.ts create mode 100644 src/question/question.service.ts diff --git a/src/question/dtos/create-question.dto.ts b/src/question/dtos/create-question.dto.ts new file mode 100644 index 0000000..c0e051c --- /dev/null +++ b/src/question/dtos/create-question.dto.ts @@ -0,0 +1,47 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsInt, IsNotEmpty, IsOptional, Min } from 'class-validator'; +export class CreateQuestionDto { + @IsNotEmpty() + @ApiProperty({ + description: 'Exam ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + examId: string; + + @IsNotEmpty() + @ApiProperty({ + description: 'Question exam', + type: String, + example: 'What is this?', + }) + question: string; + + @IsNotEmpty() + @ApiProperty({ + description: 'Type question', + type: String, + example: 'Open question', + }) + type: string; + + @IsNotEmpty() + @IsInt() + @Min(0) + @ApiProperty({ + description: 'Points in 1 question', + type: Number, + example: 0, + }) + points: number = 0; + + @IsOptional() + @IsInt() + @Min(1) + @ApiProperty({ + description: 'Order question', + type: Number, + example: 1, + }) + orderIndex?: number; +} diff --git a/src/question/dtos/question-response.dto.ts b/src/question/dtos/question-response.dto.ts new file mode 100644 index 0000000..e4be207 --- /dev/null +++ b/src/question/dtos/question-response.dto.ts @@ -0,0 +1,102 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { Question } from '../question.entity'; +import { Exam } from 'src/exam/exam.entity'; +import { ExamResponseDto } from 'src/exam/dtos/exam-response.dto'; + +export class QuestionResponseDto { + @ApiProperty({ + description: 'Question ID', + type: String, + example: '123e4567-e89b-12d3-a456-426614174000', + }) + id: string; + + @ApiProperty({ + description: 'Exam Data', + type: ExamResponseDto, + example: { + id: 'ce2fd59a-28ea-4192-bfc6-c2347450ab7e', + courseModuleId: 'b88d68fc-7437-4812-b4f6-e08f18bc09d1', + title: 'Biology', + description: 'This course is an introduction to biology', + timeLimit: 20, + passingScore: 50, + maxAttempts: 1, + shuffleQuestions: true, + status: 'draft', + createdAt: new Date(), + updatedAt: new Date(), + }, + }) + exam: Exam; + + @ApiProperty({ + description: 'Exam question', + type: String, + example: 'What is this?', + }) + question: string; + + @ApiProperty({ + description: 'Type question', + type: String, + example: 'Open question', + }) + type: string; + + @ApiProperty({ + description: 'Points in 1 question', + type: Number, + example: 0, + }) + points: Number; + + @ApiProperty({ + description: 'Score to pass exam.', + type: Number, + example: 1, + }) + orderIndex: Number; + + @ApiProperty({ + description: 'Exam created date', + type: Date, + example: new Date(), + }) + createdAt: Date; + + @ApiProperty({ + description: 'Exam updated date', + type: Date, + example: new Date(), + }) + updatedAt: Date; + + constructor(question: Question) { + this.id = question.id; + this.exam = question.exam; + this.question = question.question; + this.type = question.type; + this.points = question.points; + this.orderIndex = question.orderIndex; + this.createdAt = question.createdAt; + this.updatedAt = question.updatedAt; + } +} + +export class PaginatedQuestionResponseDto extends PaginatedResponse( + QuestionResponseDto, +) { + constructor( + question: Question[], + total: number, + pageSize: number, + currentPage: number, + ) { + const questionDtos = question.map( + (question) => new QuestionResponseDto(question), + ); + super(questionDtos, total, pageSize, currentPage); + } +} diff --git a/src/question/dtos/update-question.dto.ts b/src/question/dtos/update-question.dto.ts new file mode 100644 index 0000000..01daabc --- /dev/null +++ b/src/question/dtos/update-question.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateQuestionDto } from './create-question.dto'; + +export class UpdateQuestionDto extends PartialType(CreateQuestionDto) { } \ No newline at end of file diff --git a/src/question/question.controller.ts b/src/question/question.controller.ts new file mode 100644 index 0000000..21947a0 --- /dev/null +++ b/src/question/question.controller.ts @@ -0,0 +1,204 @@ +import { + Body, + Controller, + Delete, + Get, + HttpCode, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, +} from '@nestjs/common'; +import { ApiTags, ApiBearerAuth, ApiResponse, ApiQuery } from '@nestjs/swagger'; +import { QuestionService } from './question.service'; +import { + PaginatedQuestionResponseDto, + QuestionResponseDto, +} from './dtos/question-response.dto'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums'; +import { CreateQuestionDto } from './dtos/create-question.dto'; +import { UpdateQuestionDto } from './dtos/update-question.dto'; + +@Controller('question') +@Injectable() +@ApiTags('Question') +@ApiBearerAuth() +export class QuestionController { + constructor(private readonly questionService: QuestionService) {} + + @Get() + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all questions', + type: PaginatedQuestionResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findAll( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return await this.questionService.findAll(request, { + page: query.page, + limit: query.limit, + search: query.search, + }); + } + + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns an question', + type: QuestionResponseDto, + }) + async findOne( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const question = await this.questionService.findOne(request, { + where: { id }, + }); + return new QuestionResponseDto(question); + } + + @Get('exam/:examId') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all questions in exam', + type: PaginatedQuestionResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findQuestionByExamId( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + @Param( + 'examId', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + examId: string, + ): Promise { + return await this.questionService.findQuestionByExamId(request, examId, { + page: query.page, + limit: query.limit, + search: query.search, + }); + } + + @Post() + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Create an question', + type: QuestionResponseDto, + }) + @HttpCode(HttpStatus.CREATED) + async createQuestion( + @Body() createQuestionDto: CreateQuestionDto, + ): Promise { + const question = + await this.questionService.createQuestion(createQuestionDto); + return new QuestionResponseDto(question); + } + + @Patch(':id') + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Update an question', + type: QuestionResponseDto, + }) + async updateQuestion( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @Body() updateQuestionDto: UpdateQuestionDto, + ): Promise { + const question = await this.questionService.updateQuestion( + request, + id, + updateQuestionDto, + ); + return new QuestionResponseDto(question); + } + + @Delete(':id') + @Roles(Role.TEACHER) + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Delete an question', + }) + @HttpCode(HttpStatus.NO_CONTENT) + async deleteExam( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise<{ massage: string }> { + await this.questionService.deleteQuestion(request, id); + return { massage: 'Question deleted successfully' }; + } +} diff --git a/src/question/question.entity.ts b/src/question/question.entity.ts new file mode 100644 index 0000000..70db627 --- /dev/null +++ b/src/question/question.entity.ts @@ -0,0 +1,50 @@ +import { Exam } from "src/exam/exam.entity"; +import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn, Unique } from "typeorm"; +@Entity() +@Unique(["examId", "orderIndex"]) +export class Question { + @PrimaryGeneratedColumn("uuid") + id: string; + + @ManyToOne(() => Exam, (exam) => exam.question, { + onDelete: 'CASCADE', + nullable: false, + }) + @JoinColumn({ name: 'exam_id' }) + exam: Exam; + + @Column({ name: 'exam_id' }) + examId: string; + + @Column({ + nullable: false, + }) + question: string; + + @Column({ + nullable: false, + }) + type: string; + + @Column({ + nullable: false, + default: 0, + }) + points: number; + + @Column({ + nullable: false, + default: 1, + }) + orderIndex: number; + + @CreateDateColumn({ + type: "timestamp with time zone" + }) + createdAt: Date; + + @UpdateDateColumn({ + type: "timestamp with time zone" + }) + updatedAt: Date; +} \ No newline at end of file diff --git a/src/question/question.module.ts b/src/question/question.module.ts new file mode 100644 index 0000000..3f415f7 --- /dev/null +++ b/src/question/question.module.ts @@ -0,0 +1,22 @@ +import { Module } from "@nestjs/common"; +import { TypeOrmModule } from "@nestjs/typeorm"; +import { Question } from "./question.entity"; +import { DatabaseModule } from "src/database/database.module"; +import { questionProviders } from "./question.providers"; +import { QuestionController } from "./question.controller"; +import { QuestionService } from "./question.service"; +import { Exam } from "src/exam/exam.entity"; + +@Module({ + imports: [ + DatabaseModule, + TypeOrmModule.forFeature([Question, Exam]), + ], + controllers: [QuestionController], + providers: [ + ...questionProviders, + QuestionService, + ], + exports: [QuestionService] +}) +export class QuestionModule { } \ No newline at end of file diff --git a/src/question/question.providers.ts b/src/question/question.providers.ts new file mode 100644 index 0000000..f398911 --- /dev/null +++ b/src/question/question.providers.ts @@ -0,0 +1,8 @@ +import { DataSource } from "typeorm"; +import { Question } from "./question.entity"; + +export const questionProviders = [{ + provide: 'QuestionRepository', + useFactory: (dataSource: DataSource) => dataSource.getRepository(Question), + inject: ['DataSource'], +}]; \ No newline at end of file diff --git a/src/question/question.service.ts b/src/question/question.service.ts new file mode 100644 index 0000000..f25802b --- /dev/null +++ b/src/question/question.service.ts @@ -0,0 +1,263 @@ +import { + Injectable, + Inject, + NotFoundException, + ConflictException, + BadRequestException, +} from '@nestjs/common'; +import { Question } from './question.entity'; +import { + FindOneOptions, + FindOptionsWhere, + ILike, + Raw, + Repository, +} from 'typeorm'; +import { PaginatedQuestionResponseDto } from './dtos/question-response.dto'; +import { createPagination } from 'src/shared/pagination'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { ExamStatus, Role } from 'src/shared/enums'; +import { CreateQuestionDto } from './dtos/create-question.dto'; +import { UpdateQuestionDto } from './dtos/update-question.dto'; +import { Exam } from 'src/exam/exam.entity'; +@Injectable() +export class QuestionService { + constructor( + @Inject('QuestionRepository') + private readonly questionRepository: Repository, + @Inject('ExamRepository') + private readonly examRepository: Repository, + ) {} + + async findAll( + request: AuthenticatedRequest, + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.questionRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateCondition(request, search); + const question = await find({ + order: { + orderIndex: 'ASC', + }, + where: whereCondition, + relations: ['exam'], + }).run(); + + return question; + } + + async findOne( + request: AuthenticatedRequest, + options: FindOneOptions = {}, + ): Promise { + const whereCondition = this.validateAndCreateCondition(request, ''); + + const question = await this.questionRepository.findOne({ + ...options, + where: whereCondition, + relations: ['exam'], + }); + + if (!question) { + throw new NotFoundException('Question not found'); + } + + return question; + } + + async findQuestionByExamId( + request: AuthenticatedRequest, + examId: string, + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.questionRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateCondition(request, search); + whereCondition['exam'] = { id: examId }; + + const exam = await this.examRepository.findOne({ + where: { id: examId }, + }); + + if (!exam.shuffleQuestions) { + const question = await find({ + order: { + orderIndex: 'ASC', + }, + where: whereCondition, + }).run(); + return question; + } + + const baseQuery = this.questionRepository + .createQueryBuilder('question') + .where(whereCondition) + .orderBy('RANDOM()'); + + return await find({ + where: whereCondition, + ...{ + __queryBuilder: baseQuery, + }, + }).run(); + } + + private validateAndCreateCondition( + request: AuthenticatedRequest, + search: string, + ): FindOptionsWhere { + const baseSearch = search ? { question: ILike(`%${search}%`) } : {}; + + if (request.user.role === Role.STUDENT) { + return { + ...baseSearch, + exam: { + status: ExamStatus.PUBLISHED, + }, + }; + } + + if (request.user.role === Role.TEACHER) { + return { + ...baseSearch, + exam: { + courseModule: { + course: { + teacher: { + id: request.user.id, + }, + }, + }, + }, + }; + } + + if (request.user.role === Role.ADMIN) { + return { ...baseSearch }; + } + + return { + ...baseSearch, + exam: { + status: ExamStatus.PUBLISHED, + }, + }; + } + + async getMaxOrderIndex(examId: string): Promise { + const result = await this.questionRepository + .createQueryBuilder('question') + .select('MAX(question.orderIndex)', 'max') + .where('question.examId = :examId', { examId }) + .getRawOne(); + + return result.max ? Number(result.max) : 0; + } + + async reOrderIndex(examId: string): Promise { + const questionToReorder = await this.questionRepository.find({ + where: { examId }, + order: { orderIndex: 'ASC' }, + }); + + for (let i = 0; i < questionToReorder.length; i++) { + questionToReorder[i].orderIndex = i + 1; + } + + await this.questionRepository.save(questionToReorder); + } + + async createQuestion( + createQuestionDto: CreateQuestionDto, + ): Promise { + if (!createQuestionDto.orderIndex) { + createQuestionDto.orderIndex = + (await this.getMaxOrderIndex(createQuestionDto.examId)) + 1; + } + const exam = await this.examRepository.findOne({ + where: { id: createQuestionDto.examId }, + }); + if (!exam) { + throw new NotFoundException('Exam not found.'); + } + const question = this.questionRepository.create({ + ...createQuestionDto, + exam, + }); + try { + await this.questionRepository.save(question); + return question; + } catch (error) { + if (error.code === '23505') { + throw new ConflictException( + 'Same orderIndex already exists in this exam.', + ); + } + throw new BadRequestException( + "Can't create question. Please check your input.", + ); + } + } + + async updateQuestion( + request: AuthenticatedRequest, + id: string, + updateQuestionDto: UpdateQuestionDto, + ): Promise { + await this.findOne(request, { where: { id } }); + try { + const question = await this.questionRepository.update( + id, + updateQuestionDto, + ); + if (!question) throw new NotFoundException("Can't update question"); + return await this.questionRepository.findOne({ where: { id } }); + } catch (error) { + if (error.code === '23505') { + throw new ConflictException( + 'Same orderIndex already exists in this exam.', + ); + } + throw new BadRequestException( + "Can't update question. Please check your input.", + ); + } + } + + async deleteQuestion( + request: AuthenticatedRequest, + id: string, + ): Promise { + try { + const question = await this.findOne(request, { where: { id } }); + await this.questionRepository.delete(id); + await this.reOrderIndex(question.examId); + } catch (error) { + if (error instanceof Error) + throw new NotFoundException('Question not found'); + } + } +} diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index 66bf01e..fe27589 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -9,6 +9,7 @@ import { User } from 'src/user/user.entity'; import { DataSource, DataSourceOptions } from 'typeorm'; import { GLOBAL_CONFIG } from '../constants/global-config.constant'; import { Exam } from 'src/exam/exam.entity'; +import { Question } from 'src/question/question.entity'; const configService = new ConfigService(); @@ -20,7 +21,7 @@ export const databaseConfig: DataSourceOptions = { password: configService.get(GLOBAL_CONFIG.DB_PASSWORD), database: configService.get(GLOBAL_CONFIG.DB_DATABASE), logging: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), - entities: [User, UserStreak, Category, Course, CourseModule, Chapter, Exam], + entities: [User, UserStreak, Category, Course, CourseModule, Chapter, Exam, Question], }; export default new DataSource(databaseConfig); From 5091950555ce9571ca534e374940312e62fd4e80 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sat, 16 Nov 2024 14:43:51 +0700 Subject: [PATCH 044/155] feat: update exam response --- src/app.module.ts | 6 +- src/exam/dtos/exam-response.dto.ts | 204 ++++++++++++----------- src/exam/exam.controller.ts | 255 ++++++++++++++++------------- src/exam/exam.entity.ts | 6 + src/exam/exam.module.ts | 31 ++-- src/exam/exam.service.ts | 225 +++++++++++++------------ 6 files changed, 388 insertions(+), 339 deletions(-) diff --git a/src/app.module.ts b/src/app.module.ts index 785e2d8..487c9f9 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -22,6 +22,7 @@ import { User } from './user/user.entity'; import { UserModule } from './user/user.module'; import { FileModule } from './file/file.module'; import { ExamModule } from './exam/exam.module'; +import { QuestionModule } from './question/question.module'; const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); @@ -55,8 +56,9 @@ const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); CourseModuleModule, ChapterModule, FileModule, - ExamModule - ], + ExamModule, + QuestionModule + ], controllers: [AppController], providers: [ AppService, diff --git a/src/exam/dtos/exam-response.dto.ts b/src/exam/dtos/exam-response.dto.ts index d02e36e..416beea 100644 --- a/src/exam/dtos/exam-response.dto.ts +++ b/src/exam/dtos/exam-response.dto.ts @@ -1,117 +1,121 @@ -import { ExamStatus } from "src/shared/enums"; -import { Exam } from "../exam.entity"; -import { ApiProperty } from "@nestjs/swagger"; -import { PaginatedResponse } from "src/shared/pagination/dtos/paginate-response.dto"; -import { CourseModuleResponseDto } from "src/course-module/dtos/course-module-response.dto"; +import { ExamStatus } from 'src/shared/enums'; +import { Exam } from '../exam.entity'; +import { ApiProperty } from '@nestjs/swagger'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { CourseModuleResponseDto } from 'src/course-module/dtos/course-module-response.dto'; export class ExamResponseDto { - @ApiProperty({ - description: 'Exam ID', - type: String, - example: '123e4567-e89b-12d3-a456-426614174000', - }) - id: string; + @ApiProperty({ + description: 'Exam ID', + type: String, + example: '123e4567-e89b-12d3-a456-426614174000', + }) + id: string; - @ApiProperty({ - description: 'Course Module Data', - type: String, - example: { - "id": "8d4887aa-28e7-4d0e-844c-28a8ccead003" - }, - }) - courseModuleId: string; + @ApiProperty({ + description: 'Course Module Data', + type: String, + example: { + id: 'b88d68fc-7437-4812-b4f6-e08f18bc09d1', + title: 'Thai', + description: 'This module is an introduction to programming', + orderIndex: 1, + courseId: 'c621f01b-35dc-4448-a065-bd4a6bebb132', + createdAt: new Date(), + updatedAt: new Date(), + }, + }) + courseModule: CourseModuleResponseDto; - @ApiProperty({ - description: 'Exam title', - type: String, - example: 'Exam title', - }) - title: string; + @ApiProperty({ + description: 'Exam title', + type: String, + example: 'Exam title', + }) + title: string; - @ApiProperty({ - description: 'Exam description', - type: String, - example: 'Exam description', - }) - description: string; + @ApiProperty({ + description: 'Exam description', + type: String, + example: 'Exam description', + }) + description: string; - @ApiProperty({ - description: 'Timelimit to do exam.', - type: Number, - example: 20, - }) - timeLimit: Number; + @ApiProperty({ + description: 'Timelimit to do exam.', + type: Number, + example: 20, + }) + timeLimit: Number; - @ApiProperty({ - description: 'Score to pass exam.', - type: Number, - example: 20, - }) - passingScore: Number; + @ApiProperty({ + description: 'Score to pass exam.', + type: Number, + example: 20, + }) + passingScore: Number; - @ApiProperty({ - description: 'Max attempts to do exam.', - type: Number, - example: 1, - }) - maxAttempts: Number; + @ApiProperty({ + description: 'Max attempts to do exam.', + type: Number, + example: 1, + }) + maxAttempts: Number; - @ApiProperty({ - description: 'Shuffle question', - type: Boolean, - example: false, - }) - shuffleQuestions: Boolean; + @ApiProperty({ + description: 'Shuffle question', + type: Boolean, + example: false, + }) + shuffleQuestions: Boolean; - @ApiProperty({ - description: 'Exam status', - type: String, - enum: ExamStatus, - example: ExamStatus.DRAFT, - }) - status: ExamStatus; + @ApiProperty({ + description: 'Exam status', + type: String, + enum: ExamStatus, + example: ExamStatus.DRAFT, + }) + status: ExamStatus; - @ApiProperty({ - description: 'Exam created date', - type: Date, - example: new Date(), - }) - createdAt: Date; + @ApiProperty({ + description: 'Exam created date', + type: Date, + example: new Date(), + }) + createdAt: Date; - @ApiProperty({ - description: 'Exam updated date', - type: Date, - example: new Date(), - }) - updatedAt: Date; + @ApiProperty({ + description: 'Exam updated date', + type: Date, + example: new Date(), + }) + updatedAt: Date; - constructor(exam: Exam) { - this.id = exam.id; - this.courseModuleId = exam.courseModuleId - this.title = exam.title; - this.description = exam.description; - this.timeLimit = exam.timeLimit; - this.passingScore = exam.passingScore; - this.maxAttempts = exam.maxAttempts; - this.shuffleQuestions = exam.shuffleQuestions; - this.status = exam.status; - this.createdAt = exam.createdAt; - this.updatedAt = exam.updatedAt; - } + constructor(exam: Exam) { + this.id = exam.id; + this.courseModule = exam.courseModule; + this.title = exam.title; + this.description = exam.description; + this.timeLimit = exam.timeLimit; + this.passingScore = exam.passingScore; + this.maxAttempts = exam.maxAttempts; + this.shuffleQuestions = exam.shuffleQuestions; + this.status = exam.status; + this.createdAt = exam.createdAt; + this.updatedAt = exam.updatedAt; + } } export class PaginatedExamResponseDto extends PaginatedResponse( - ExamResponseDto, + ExamResponseDto, ) { - constructor( - Exam: Exam[], - total: number, - pageSize: number, - currentPage: number, - ) { - const ExamDtos = Exam.map( - (exam) => new ExamResponseDto(exam), - ); - super(ExamDtos, total, pageSize, currentPage); - } -} \ No newline at end of file + constructor( + exam: Exam[], + total: number, + pageSize: number, + currentPage: number, + ) { + const examDtos = exam.map((exam) => new ExamResponseDto(exam)); + super(examDtos, total, pageSize, currentPage); + } +} diff --git a/src/exam/exam.controller.ts b/src/exam/exam.controller.ts index 2ae67bc..6fc3817 100644 --- a/src/exam/exam.controller.ts +++ b/src/exam/exam.controller.ts @@ -1,141 +1,160 @@ import { - Controller, - Injectable, - Get, - Param, - ParseUUIDPipe, - HttpStatus, - Post, - Body, - Patch, - Delete, - HttpCode, - Query, - Req, + Controller, + Injectable, + Get, + Param, + ParseUUIDPipe, + HttpStatus, + Post, + Body, + Patch, + Delete, + HttpCode, + Query, + Req, } from '@nestjs/common'; -import { ApiTags, ApiBearerAuth, ApiResponse, ApiQuery, ApiParam } from '@nestjs/swagger'; +import { + ApiTags, + ApiBearerAuth, + ApiResponse, + ApiQuery, + ApiParam, +} from '@nestjs/swagger'; import { ExamService } from './exam.service'; -import { ExamResponseDto, PaginatedExamResponseDto } from './dtos/exam-response.dto'; +import { + ExamResponseDto, + PaginatedExamResponseDto, +} from './dtos/exam-response.dto'; import { Roles } from 'src/shared/decorators/role.decorator'; import { Role } from 'src/shared/enums'; import { CreateExamDto } from './dtos/create-exam.dto'; import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { UpdateExamDto } from './dtos/update-exam.dto'; +import { PaginatedQuestionResponseDto } from 'src/question/dtos/question-response.dto'; @Controller('exam') @ApiTags('Exam') @ApiBearerAuth() @Injectable() export class ExamController { - constructor(private readonly examService: ExamService) { } + constructor(private readonly examService: ExamService) {} - @Get() - @ApiResponse({ - status: HttpStatus.OK, - description: 'Returns all exams', - type: ExamResponseDto, - isArray: true, - }) - @ApiQuery({ - name: 'page', - type: Number, - required: false, - description: 'Page number', - }) - @ApiQuery({ - name: 'limit', - type: Number, - required: false, - description: 'Items per page', - }) - @ApiQuery({ - name: 'search', - type: String, - required: false, - description: 'Search by title', - }) - async findAll(@Req() request: AuthenticatedRequest, @Query() query: PaginateQueryDto): Promise { - return await this.examService.findAll(request, { - page: query.page, - limit: query.limit, - search: query.search, - }); - } + @Get() + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all exams', + type: PaginatedQuestionResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findAll( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return await this.examService.findAll(request, { + page: query.page, + limit: query.limit, + search: query.search, + }); + } - @Get(':id') - @ApiResponse({ - status: HttpStatus.OK, - description: 'Returns an exam', - type: ExamResponseDto, - }) - async findOne( - @Req() request: AuthenticatedRequest, - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - ): Promise { - const exam = await this.examService.findOne(request, { where: { id } }); - return new ExamResponseDto(exam); - } + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns an exam', + type: ExamResponseDto, + }) + async findOne( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const exam = await this.examService.findOne(request, { where: { id } }); + return new ExamResponseDto(exam); + } - @Post() - @Roles(Role.TEACHER) - @ApiResponse({ - status: HttpStatus.CREATED, - description: 'Create an exam', - type: ExamResponseDto, - }) - @HttpCode(HttpStatus.CREATED) - async createExam(@Body() createExamDto: CreateExamDto - ): Promise { - const exam = await this.examService.createExam(createExamDto); - return new ExamResponseDto(exam); - } + @Post() + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Create an exam', + type: ExamResponseDto, + }) + @HttpCode(HttpStatus.CREATED) + async createExam( + @Body() createExamDto: CreateExamDto, + ): Promise { + const exam = await this.examService.createExam(createExamDto); + return new ExamResponseDto(exam); + } - @Patch(':id') - @Roles(Role.TEACHER) - @ApiResponse({ - status: HttpStatus.OK, - description: 'Update an exam', - type: ExamResponseDto, - }) - async updateExam(@Req() request: AuthenticatedRequest, @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), + @Patch(':id') + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Update an exam', + type: ExamResponseDto, + }) + async updateExam( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), ) - id: string, @Body() updateExamDto: UpdateExamDto - ): Promise { - const exam = await this.examService.updateExam(request, id, updateExamDto); - return new ExamResponseDto(exam); - } + id: string, + @Body() updateExamDto: UpdateExamDto, + ): Promise { + const exam = await this.examService.updateExam(request, id, updateExamDto); + return new ExamResponseDto(exam); + } - @Delete(':id') - @Roles(Role.TEACHER) - @Roles(Role.ADMIN) - @ApiResponse({ - status: HttpStatus.NO_CONTENT, - description: 'Delete an exam', - }) - @HttpCode(HttpStatus.NO_CONTENT) - async deleteExam(@Req() request: AuthenticatedRequest, @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), + @Delete(':id') + @Roles(Role.TEACHER) + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Delete an exam', + }) + @HttpCode(HttpStatus.NO_CONTENT) + async deleteExam( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), ) id: string, - ): Promise<{ massage: string }> { - await this.examService.deleteExam(request, id); - return { massage: 'Exam deleted successfully' }; - } -} \ No newline at end of file + ): Promise<{ massage: string }> { + await this.examService.deleteExam(request, id); + return { massage: 'Exam deleted successfully' }; + } +} diff --git a/src/exam/exam.entity.ts b/src/exam/exam.entity.ts index a839a1d..be707b0 100644 --- a/src/exam/exam.entity.ts +++ b/src/exam/exam.entity.ts @@ -1,6 +1,7 @@ import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany, OneToOne, JoinColumn } from "typeorm"; import { ExamStatus } from "src/shared/enums"; import { CourseModule } from "src/course-module/course-module.entity"; +import { Question } from "src/question/question.entity"; @Entity() export class Exam { @@ -17,6 +18,11 @@ export class Exam { @Column({ name: 'course_module_id' }) courseModuleId: string; + @OneToMany(() => Question, (question) => question.exam, { + cascade: true, + }) + question: Question; + @Column({ nullable: false, }) diff --git a/src/exam/exam.module.ts b/src/exam/exam.module.ts index 2323848..39b0384 100644 --- a/src/exam/exam.module.ts +++ b/src/exam/exam.module.ts @@ -1,21 +1,16 @@ -import { Module } from "@nestjs/common"; -import { DatabaseModule } from "src/database/database.module"; -import { ExamController } from "./exam.controller"; -import { ExamService } from "./exam.service"; -import { TypeOrmModule } from "@nestjs/typeorm"; -import { Exam } from "./exam.entity"; -import { examProviders } from "./exam.providers"; +import { Module } from '@nestjs/common'; +import { DatabaseModule } from 'src/database/database.module'; +import { ExamController } from './exam.controller'; +import { ExamService } from './exam.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Exam } from './exam.entity'; +import { examProviders } from './exam.providers'; +import { CourseModule } from 'src/course-module/course-module.entity'; @Module({ - imports: [ - DatabaseModule, - TypeOrmModule.forFeature([Exam]), - ], - controllers: [ExamController], - providers: [ - ...examProviders, - ExamService, - ], - exports: [ExamService] + imports: [DatabaseModule, TypeOrmModule.forFeature([Exam, CourseModule])], + controllers: [ExamController], + providers: [...examProviders, ExamService], + exports: [ExamService], }) -export class ExamModule { } \ No newline at end of file +export class ExamModule {} diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts index c07f4a2..3e21d43 100644 --- a/src/exam/exam.service.ts +++ b/src/exam/exam.service.ts @@ -1,117 +1,140 @@ -import { Injectable, Inject, NotFoundException } from "@nestjs/common"; -import { Repository, FindOneOptions, ILike, FindOptionsWhere, FindOptionsSelect } from "typeorm"; -import { Exam } from "./exam.entity"; -import { CreateExamDto } from "./dtos/create-exam.dto"; -import { PaginatedExamResponseDto } from "./dtos/exam-response.dto"; -import { createPagination } from "src/shared/pagination"; -import { ExamStatus, Role } from "src/shared/enums"; -import { AuthenticatedRequest } from "src/auth/interfaces/authenticated-request.interface"; -import { UpdateExamDto } from "./dtos/update-exam.dto"; +import { Injectable, Inject, NotFoundException } from '@nestjs/common'; +import { + Repository, + FindOneOptions, + ILike, + FindOptionsWhere, + FindOptionsSelect, +} from 'typeorm'; +import { Exam } from './exam.entity'; +import { CreateExamDto } from './dtos/create-exam.dto'; +import { PaginatedExamResponseDto } from './dtos/exam-response.dto'; +import { createPagination } from 'src/shared/pagination'; +import { ExamStatus, Role } from 'src/shared/enums'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { UpdateExamDto } from './dtos/update-exam.dto'; +import { CourseModule } from 'src/course-module/course-module.entity'; @Injectable() export class ExamService { - constructor( - @Inject('ExamRepository') - private readonly examRepository: Repository, - ) { } - - async findAll(request: AuthenticatedRequest, { - page = 1, - limit = 20, - search = '', + constructor( + @Inject('ExamRepository') + private readonly examRepository: Repository, + @Inject('CourseModuleRepository') + private readonly courseModuleRepository: Repository, + ) {} + + async findAll( + request: AuthenticatedRequest, + { + page = 1, + limit = 20, + search = '', }: { - page?: number; - limit?: number; - search?: string; - }): Promise { - const { find } = await createPagination(this.examRepository, { - page, - limit, - }); - - const whereCondition = this.validateAndCreateCondition(request, search) - const exam = await find({ - where: whereCondition, - relations: { - // Only load courseModule relation, not course or teacher - courseModule: true, - }, - }).run(); + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.examRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateCondition(request, search); + const exam = await find({ + where: whereCondition, + relations: ['courseModule'], + }).run(); + + return exam; + } + + private validateAndCreateCondition( + request: AuthenticatedRequest, + search: string, + ): FindOptionsWhere { + const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; + + if (request.user.role === Role.STUDENT) { + return { ...baseSearch, status: ExamStatus.PUBLISHED }; + } - return exam; + if (request.user.role === Role.TEACHER) { + return { + ...baseSearch, + courseModule: { + course: { + teacher: { + id: request.user.id, + }, + }, + }, + }; } - private validateAndCreateCondition(request: AuthenticatedRequest, search: string): FindOptionsWhere { - const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; - - if (request.user.role === Role.STUDENT) { - return { ...baseSearch, status: ExamStatus.PUBLISHED }; - } - - if (request.user.role === Role.TEACHER) { - return { - ...baseSearch, - courseModule: { - course: { - teacher: { - id: request.user.id, - } - } - } - }; - } - - if (request.user.role === Role.ADMIN) { - return { ...baseSearch }; - } - - return { ...baseSearch, status: ExamStatus.PUBLISHED }; + if (request.user.role === Role.ADMIN) { + return { ...baseSearch }; } - async findOne(request: AuthenticatedRequest, options: FindOneOptions = {}): Promise { - const whereCondition = this.validateAndCreateCondition(request, ''); + return { ...baseSearch, status: ExamStatus.PUBLISHED }; + } - const exam = await this.examRepository.findOne({ - ...options, - where: whereCondition, - relations: { - courseModule: true, - }, - }); + async findOne( + request: AuthenticatedRequest, + options: FindOneOptions = {}, + ): Promise { + const whereCondition = this.validateAndCreateCondition(request, ''); - if (!exam) { - throw new NotFoundException('Exam not found'); - } + const exam = await this.examRepository.findOne({ + ...options, + where: whereCondition, + relations: ['courseModule'], + }); - return exam; + if (!exam) { + throw new NotFoundException('Exam not found'); } - async createExam(createExamDto: CreateExamDto): Promise { - const ExamModule = this.examRepository.create(createExamDto); - await this.examRepository.save(ExamModule); - if (!ExamModule) - throw new NotFoundException("Can't create exam"); - return ExamModule; + return exam; + } + + async createExam(createExamDto: CreateExamDto): Promise { + const courseModule = await this.courseModuleRepository.findOne({ + where: { id: createExamDto.courseModuleId }, + }); + + const exam = this.examRepository.create({ ...createExamDto, courseModule }); + + await this.examRepository.save(exam); + if (!exam) throw new NotFoundException("Can't create exam"); + return exam; + } + + async updateExam( + request: AuthenticatedRequest, + id: string, + updateExamDto: UpdateExamDto, + ): Promise { + const examInData = await this.findOne(request, { where: { id } }); + if ( + examInData.status != ExamStatus.DRAFT && + updateExamDto.status == ExamStatus.DRAFT + ) { + throw new NotFoundException("Can't change status to draft"); } - - async updateExam(request: AuthenticatedRequest, id: string, updateExamDto: UpdateExamDto): Promise { - const examInData = await this.findOne(request, { where: { id } }) - if (examInData.status != ExamStatus.DRAFT && updateExamDto.status == ExamStatus.DRAFT) { - throw new NotFoundException("Can't change status to draft"); - } - const exam = await this.examRepository.update(id, updateExamDto); - if (!exam) - throw new NotFoundException("Can't update exam"); - return await this.examRepository.findOne({ where: { id } }); - } - - async deleteExam(request: AuthenticatedRequest, id: string): Promise { - try { - if (await this.findOne(request, { where: { id } })) { - await this.examRepository.delete(id); - } - } catch (error) { - if (error instanceof Error) throw new NotFoundException('Exam not found'); - } + const exam = await this.examRepository.update(id, updateExamDto); + if (!exam) throw new NotFoundException("Can't update exam"); + return await this.examRepository.findOne({ where: { id } }); + } + + async deleteExam(request: AuthenticatedRequest, id: string): Promise { + try { + if (await this.findOne(request, { where: { id } })) { + await this.examRepository.delete(id); + } + } catch (error) { + if (error instanceof Error) throw new NotFoundException('Exam not found'); } -} \ No newline at end of file + } +} From bc7eca0dbe653e5a219aabce087fb835baf556c6 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sat, 16 Nov 2024 14:50:33 +0700 Subject: [PATCH 045/155] fix: fix duplicate import --- src/app.module.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/app.module.ts b/src/app.module.ts index e408af1..3a4c335 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -23,8 +23,6 @@ import { UserStreak } from './user-streak/user-streak.entity'; import { UserStreakModule } from './user-streak/user-streak.module'; import { User } from './user/user.entity'; import { UserModule } from './user/user.module'; -import { FileModule } from './file/file.module'; -import { ExamModule } from './exam/exam.module'; import { QuestionModule } from './question/question.module'; const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); From 168fd8964b1c709c4b5ca37ea3ad5612b5754591 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Sat, 16 Nov 2024 15:47:40 +0700 Subject: [PATCH 046/155] feat: implement chat room module with DTOs, enums, and entity; update chapter entity and module integration --- src/app.module.ts | 2 + src/chapter/chapter.entity.ts | 5 +- src/chapter/chapter.module.ts | 3 +- src/chat-room/chat-room.controller.ts | 133 +++++++++++++++++- src/chat-room/chat-room.entity.ts | 22 ++- src/chat-room/chat-room.module.ts | 9 +- src/chat-room/chat-room.providers.ts | 10 ++ src/chat-room/chat-room.service.ts | 83 ++++++++++- src/chat-room/dtos/create-chat-room.dto.ts | 41 ++++++ src/chat-room/dtos/index.ts | 3 + .../dtos/paginated-user-response.dto.ts | 65 +++++++++ src/chat-room/dtos/update-chat-room.dto.ts | 32 +++++ src/chat-room/enums/chat-room-status.enum.ts | 5 + src/chat-room/enums/chat-room-type.enum.ts | 4 + src/chat-room/enums/index.ts | 2 + src/shared/configs/database.config.ts | 14 +- 16 files changed, 413 insertions(+), 20 deletions(-) create mode 100644 src/chat-room/chat-room.providers.ts create mode 100644 src/chat-room/dtos/create-chat-room.dto.ts create mode 100644 src/chat-room/dtos/index.ts create mode 100644 src/chat-room/dtos/paginated-user-response.dto.ts create mode 100644 src/chat-room/dtos/update-chat-room.dto.ts create mode 100644 src/chat-room/enums/chat-room-status.enum.ts create mode 100644 src/chat-room/enums/chat-room-type.enum.ts create mode 100644 src/chat-room/enums/index.ts diff --git a/src/app.module.ts b/src/app.module.ts index 7f57ce4..79c255d 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -21,6 +21,7 @@ import { GLOBAL_CONFIG } from './shared/constants/global-config.constant'; import { RolesGuard } from './shared/guards/role.guard'; import { UserStreakModule } from './user-streak/user-streak.module'; import { UserModule } from './user/user.module'; +import { ChatRoomModule } from './chat-room/chat-room.module'; @Module({ imports: [ @@ -53,6 +54,7 @@ import { UserModule } from './user/user.module'; FileModule, ExamModule, EnrollmentModule, + ChatRoomModule, ], controllers: [AppController], providers: [ diff --git a/src/chapter/chapter.entity.ts b/src/chapter/chapter.entity.ts index 85c64b2..2fef681 100644 --- a/src/chapter/chapter.entity.ts +++ b/src/chapter/chapter.entity.ts @@ -30,9 +30,12 @@ export class Chapter { onDelete: 'CASCADE', nullable: false, }) - @JoinColumn() + @JoinColumn({ name: 'module_id' }) module: CourseModule; + @Column({ name: 'module_id' }) + moduleId: string; + @Column({ type: String, nullable: false, diff --git a/src/chapter/chapter.module.ts b/src/chapter/chapter.module.ts index 8386fa3..b858f41 100644 --- a/src/chapter/chapter.module.ts +++ b/src/chapter/chapter.module.ts @@ -5,11 +5,12 @@ import { Chapter } from './chapter.entity'; import { chapterProviders } from './chapter.provider'; import { ChapterService } from './chapter.service'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { CourseModule } from 'src/course-module/course-module.entity'; @Module({ imports: [ DatabaseModule, - TypeOrmModule.forFeature([Chapter]), + TypeOrmModule.forFeature([Chapter, CourseModule]), ], controllers: [ChapterController], providers: [...chapterProviders, ChapterService], diff --git a/src/chat-room/chat-room.controller.ts b/src/chat-room/chat-room.controller.ts index 46165bc..cf15eae 100644 --- a/src/chat-room/chat-room.controller.ts +++ b/src/chat-room/chat-room.controller.ts @@ -1,7 +1,136 @@ -import { Controller, Injectable } from "@nestjs/common"; +import { Controller, Injectable, Query, HttpStatus, Param, ParseUUIDPipe, Post, HttpCode, Body, Patch, Delete } from "@nestjs/common"; +import { Get } from "@nestjs/common"; +import { ChatRoomService } from "./chat-room.service"; +import { PaginateQueryDto } from "src/shared/pagination/dtos/paginate-query.dto"; +import { ApiTags, ApiBearerAuth, ApiResponse, ApiQuery, ApiParam } from "@nestjs/swagger"; +import { ChatRoomResponseDto, PaginatedChatRoomResponseDto } from "./dtos/paginated-user-response.dto"; +import { Roles } from "src/shared/decorators/role.decorator"; +import { Role } from "src/shared/enums"; +import { CreateChatRoomDto } from "./dtos/create-chat-room.dto"; +import { UpdateChatRoomDto } from "./dtos/update-chat-room.dto"; @Controller('chat-room') @Injectable() +@ApiTags('Chat Room') +@ApiBearerAuth() export class ChatRoomController { - constructor() { } + constructor( + private readonly chatRoomService: ChatRoomService, + ) { } + + @Get() + @ApiResponse({ + status: HttpStatus.OK, + description: 'Get all chat rooms', + type: PaginatedChatRoomResponseDto, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + }) + @Roles(Role.ADMIN) + async findAll( + @Query() query: PaginateQueryDto + ) { + return await this.chatRoomService.findAll(query); + } + + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Get chat room by id', + type: ChatRoomResponseDto, + }) + @ApiParam({ + name: 'id', + type: String, + required: true, + }) + @Roles(Role.ADMIN) + async findById( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }) + ) id: string + ) { + return new ChatRoomResponseDto(await this.chatRoomService.findOne({ where: { id } })); + } + + @Post() + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Create chat room', + type: ChatRoomResponseDto, + }) + @Roles(Role.ADMIN) + @HttpCode(HttpStatus.CREATED) + async create( + @Body() createChatRoomDto: CreateChatRoomDto, + ) { + return new ChatRoomResponseDto(await this.chatRoomService.create(createChatRoomDto)); + } + + @Patch(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Update chat room', + type: ChatRoomResponseDto, + }) + @ApiParam({ + name: 'id', + type: String, + required: true, + }) + @Roles(Role.ADMIN) + async update( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }) + ) id: string, + @Body() updateChatRoomDto: UpdateChatRoomDto, + ) { + return new ChatRoomResponseDto(await this.chatRoomService.update({ id }, updateChatRoomDto)); + } + + @Delete(':id') + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Delete chat room', + type: ChatRoomResponseDto, + }) + @ApiParam({ + name: 'id', + type: String, + required: true, + }) + @Roles(Role.ADMIN) + @HttpCode(HttpStatus.NO_CONTENT) + async delete( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }) + ) id: string, + ) { + return await this.chatRoomService.delete({ id }); + } } \ No newline at end of file diff --git a/src/chat-room/chat-room.entity.ts b/src/chat-room/chat-room.entity.ts index 4872fd1..cc50cf8 100644 --- a/src/chat-room/chat-room.entity.ts +++ b/src/chat-room/chat-room.entity.ts @@ -1,12 +1,18 @@ -import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToOne } from "typeorm"; +import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToOne, JoinColumn } from "typeorm"; import { Chapter } from "src/chapter/chapter.entity"; +import { ChatRoomType, ChatRoomStatus } from "./enums"; @Entity() export class ChatRoom { @PrimaryGeneratedColumn("uuid") - id: number; + id: string; - @OneToOne(() => Chapter, { onDelete: "CASCADE" }) + @OneToOne(() => Chapter, { + onDelete: "CASCADE", + nullable: false, + eager: true, + }) + @JoinColumn({ name: "chapter_id" }) chapter: Chapter; @Column({ @@ -16,13 +22,19 @@ export class ChatRoom { @Column({ nullable: false, + type: "enum", + enum: ChatRoomType, + default: ChatRoomType.QUESTION, }) - type: string; + type: ChatRoomType; @Column({ nullable: false, + type: "enum", + enum: ChatRoomStatus, + default: ChatRoomStatus.ACTIVE, }) - status: string; + status: ChatRoomStatus; @CreateDateColumn({ type: "timestamp", diff --git a/src/chat-room/chat-room.module.ts b/src/chat-room/chat-room.module.ts index 3e91c20..83a59f7 100644 --- a/src/chat-room/chat-room.module.ts +++ b/src/chat-room/chat-room.module.ts @@ -2,12 +2,19 @@ import { Module } from "@nestjs/common"; import { DatabaseModule } from "src/database/database.module"; import { ChatRoomController } from "./chat-room.controller"; import { ChatRoomService } from "./chat-room.service"; +import { TypeOrmModule } from "@nestjs/typeorm"; +import { ChatRoom } from "./chat-room.entity"; +import { chatRoomProviders } from "./chat-room.providers"; @Module({ imports: [ DatabaseModule, + TypeOrmModule.forFeature([ChatRoom]), ], controllers: [ChatRoomController], - providers: [ChatRoomService], + providers: [ + ...chatRoomProviders, + ChatRoomService, + ], }) export class ChatRoomModule { } \ No newline at end of file diff --git a/src/chat-room/chat-room.providers.ts b/src/chat-room/chat-room.providers.ts new file mode 100644 index 0000000..74b045a --- /dev/null +++ b/src/chat-room/chat-room.providers.ts @@ -0,0 +1,10 @@ +import { DataSource } from 'typeorm'; +import { ChatRoom } from './chat-room.entity'; + +export const chatRoomProviders = [ + { + provide: 'ChatRoomRepository', + useFactory: (dataSource: DataSource) => dataSource.getRepository(ChatRoom), + inject: ['DataSource'], + }, +]; diff --git a/src/chat-room/chat-room.service.ts b/src/chat-room/chat-room.service.ts index f73d7bb..845ebb5 100644 --- a/src/chat-room/chat-room.service.ts +++ b/src/chat-room/chat-room.service.ts @@ -1,6 +1,83 @@ -import { Injectable } from "@nestjs/common"; +import { + Injectable, + Inject, + InternalServerErrorException, + NotFoundException, +} from '@nestjs/common'; +import { Repository, FindOneOptions, FindOptionsWhere } from 'typeorm'; +import { ChatRoom } from './chat-room.entity'; +import { createPagination } from 'src/shared/pagination'; +import { + UpdateChatRoomDto, + PaginatedChatRoomResponseDto, + CreateChatRoomDto, +} from './dtos'; @Injectable() export class ChatRoomService { - constructor() { } -} \ No newline at end of file + constructor( + @Inject('ChatRoomRepository') + private readonly chatRoomRepository: Repository, + ) { } + + async create(createChatRoomDto: CreateChatRoomDto): Promise { + try { + return await this.chatRoomRepository.save(createChatRoomDto); + } catch (error) { + if (error instanceof Error) + throw new InternalServerErrorException(error.message); + } + } + + async findAll({ + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }): Promise { + const { find } = await createPagination(this.chatRoomRepository, { + page, + limit, + }); + const chatRooms = await find({ + where: { chapter: { id: search } }, + }).run(); + return new PaginatedChatRoomResponseDto( + chatRooms.data, + chatRooms.meta.total, + chatRooms.meta.pageSize, + chatRooms.meta.currentPage, + ); + } + + async findOne(options: FindOneOptions): Promise { + try { + return await this.chatRoomRepository.findOne(options); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); + } + } + + async update( + criteria: FindOptionsWhere, + updateChatRoomDto: UpdateChatRoomDto, + ): Promise { + try { + await this.chatRoomRepository.update(criteria, updateChatRoomDto); + return await this.findOne({ where: criteria }); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); + } + } + + async delete(criteria: FindOptionsWhere): Promise { + try { + await this.chatRoomRepository.delete(criteria); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); + } + } +} diff --git a/src/chat-room/dtos/create-chat-room.dto.ts b/src/chat-room/dtos/create-chat-room.dto.ts new file mode 100644 index 0000000..3ddbe06 --- /dev/null +++ b/src/chat-room/dtos/create-chat-room.dto.ts @@ -0,0 +1,41 @@ +import { IsString, IsEnum, IsUUID, IsNotEmpty } from "class-validator"; +import { ChatRoomType, ChatRoomStatus } from "../enums"; +import { ApiProperty } from "@nestjs/swagger"; + +export class CreateChatRoomDto { + @IsString() + @IsNotEmpty() + @ApiProperty({ + description: 'ChatRoom name', + type: String, + example: 'Chat Room 1', + }) + name: string; + + @IsEnum(ChatRoomType) + @ApiProperty({ + description: 'ChatRoom type', + type: String, + example: ChatRoomType.QUESTION, + enum: ChatRoomType, + }) + type: ChatRoomType; + + @IsEnum(ChatRoomStatus) + @ApiProperty({ + description: 'ChatRoom status', + type: String, + example: ChatRoomStatus.ACTIVE, + enum: ChatRoomStatus, + }) + status: ChatRoomStatus; + + @IsUUID('4') + @IsNotEmpty() + @ApiProperty({ + description: 'Chapter ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + chapterId: string; +} \ No newline at end of file diff --git a/src/chat-room/dtos/index.ts b/src/chat-room/dtos/index.ts new file mode 100644 index 0000000..2e62389 --- /dev/null +++ b/src/chat-room/dtos/index.ts @@ -0,0 +1,3 @@ +export * from './create-chat-room.dto'; +export * from './paginated-user-response.dto'; +export * from './update-chat-room.dto'; \ No newline at end of file diff --git a/src/chat-room/dtos/paginated-user-response.dto.ts b/src/chat-room/dtos/paginated-user-response.dto.ts new file mode 100644 index 0000000..4a9dc59 --- /dev/null +++ b/src/chat-room/dtos/paginated-user-response.dto.ts @@ -0,0 +1,65 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { ChatRoom } from '../chat-room.entity'; +import { ChatRoomType, ChatRoomStatus } from '../enums'; + +export class ChatRoomResponseDto { + @ApiProperty({ + description: 'ChatRoom ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + id: string; + + @ApiProperty({ + description: 'ChatRoom title', + type: String, + example: 'Chat Room 1', + }) + title: string; + + @ApiProperty({ + description: 'ChatRoom status', + type: String, + example: ChatRoomStatus.ACTIVE, + enum: ChatRoomStatus, + }) + status: ChatRoomStatus; + + @ApiProperty({ + description: 'ChatRoom type', + type: String, + example: ChatRoomType.QUESTION, + enum: ChatRoomType, + }) + type: ChatRoomType; + + @ApiProperty({ + description: 'ChatRoom participant count', + type: Number, + example: 5, + }) + paticipantCount: number; + + constructor(chatRoom: ChatRoom) { + this.id = chatRoom.id; + this.title = chatRoom.title; + this.status = chatRoom.status; + this.type = chatRoom.type; + this.paticipantCount = chatRoom.participantCount; + } +} + +export class PaginatedChatRoomResponseDto extends PaginatedResponse( + ChatRoomResponseDto, +) { + constructor( + chatRooms: ChatRoom[], + total: number, + pageSize: number, + currentPage: number, + ) { + const chatRoomDtos = chatRooms.map((chatRoom) => new ChatRoomResponseDto(chatRoom)); + super(chatRoomDtos, total, pageSize, currentPage); + } +} diff --git a/src/chat-room/dtos/update-chat-room.dto.ts b/src/chat-room/dtos/update-chat-room.dto.ts new file mode 100644 index 0000000..88e3f4f --- /dev/null +++ b/src/chat-room/dtos/update-chat-room.dto.ts @@ -0,0 +1,32 @@ +import { IsString, IsEnum, IsNotEmpty } from "class-validator"; +import { ChatRoomType, ChatRoomStatus } from "../enums"; +import { ApiProperty } from "@nestjs/swagger"; + +export class UpdateChatRoomDto { + @IsString() + @IsNotEmpty() + @ApiProperty({ + description: 'ChatRoom name', + type: String, + example: 'Chat Room 1', + }) + name: string; + + @IsEnum(ChatRoomType) + @ApiProperty({ + description: 'ChatRoom type', + type: String, + example: ChatRoomType.QUESTION, + enum: ChatRoomType, + }) + type: ChatRoomType; + + @IsEnum(ChatRoomStatus) + @ApiProperty({ + description: 'ChatRoom status', + type: String, + example: ChatRoomStatus.ACTIVE, + enum: ChatRoomStatus, + }) + status: ChatRoomStatus; +} \ No newline at end of file diff --git a/src/chat-room/enums/chat-room-status.enum.ts b/src/chat-room/enums/chat-room-status.enum.ts new file mode 100644 index 0000000..61983f1 --- /dev/null +++ b/src/chat-room/enums/chat-room-status.enum.ts @@ -0,0 +1,5 @@ +export enum ChatRoomStatus { + ACTIVE = 'active', + CLOSED = 'closed', + ARCHIVED = 'archived', +} \ No newline at end of file diff --git a/src/chat-room/enums/chat-room-type.enum.ts b/src/chat-room/enums/chat-room-type.enum.ts new file mode 100644 index 0000000..1288f28 --- /dev/null +++ b/src/chat-room/enums/chat-room-type.enum.ts @@ -0,0 +1,4 @@ +export enum ChatRoomType { + QUESTION = 'question', + DISCUSSION = 'discussion', +} \ No newline at end of file diff --git a/src/chat-room/enums/index.ts b/src/chat-room/enums/index.ts new file mode 100644 index 0000000..60c4c3b --- /dev/null +++ b/src/chat-room/enums/index.ts @@ -0,0 +1,2 @@ +export * from './chat-room-status.enum'; +export * from './chat-room-type.enum'; \ No newline at end of file diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index 614c393..3e643ca 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -6,13 +6,13 @@ import { GLOBAL_CONFIG } from '../constants/global-config.constant'; const configService = new ConfigService(); export const databaseConfig: DataSourceOptions = { - type: 'postgres', - host: configService.get(GLOBAL_CONFIG.DB_HOST), - port: configService.get(GLOBAL_CONFIG.DB_PORT), - username: configService.get(GLOBAL_CONFIG.DB_USERNAME), - password: configService.get(GLOBAL_CONFIG.DB_PASSWORD), - database: configService.get(GLOBAL_CONFIG.DB_DATABASE), - logging: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), + type: 'postgres', + host: configService.get(GLOBAL_CONFIG.DB_HOST), + port: configService.get(GLOBAL_CONFIG.DB_PORT), + username: configService.get(GLOBAL_CONFIG.DB_USERNAME), + password: configService.get(GLOBAL_CONFIG.DB_PASSWORD), + database: configService.get(GLOBAL_CONFIG.DB_DATABASE), + logging: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), }; export default new DataSource(databaseConfig); From 4a6b5e91db423f3a0796c1efbfc54b00e1bd7f4f Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sat, 16 Nov 2024 16:04:34 +0700 Subject: [PATCH 047/155] feat: create enum type question --- src/question/dtos/create-question.dto.ts | 83 ++++++++-------- src/question/dtos/question-response.dto.ts | 6 +- src/question/question.entity.ts | 106 +++++++++++---------- src/question/question.service.ts | 15 +-- src/shared/enums/index.ts | 3 +- src/shared/enums/question-type.enum.ts | 5 + 6 files changed, 121 insertions(+), 97 deletions(-) create mode 100644 src/shared/enums/question-type.enum.ts diff --git a/src/question/dtos/create-question.dto.ts b/src/question/dtos/create-question.dto.ts index c0e051c..552a002 100644 --- a/src/question/dtos/create-question.dto.ts +++ b/src/question/dtos/create-question.dto.ts @@ -1,47 +1,50 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsInt, IsNotEmpty, IsOptional, Min } from 'class-validator'; +import { IsEnum, IsInt, IsNotEmpty, IsOptional, Min } from 'class-validator'; +import { QuestionType } from 'src/shared/enums'; export class CreateQuestionDto { - @IsNotEmpty() - @ApiProperty({ - description: 'Exam ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }) - examId: string; + @IsNotEmpty() + @ApiProperty({ + description: 'Exam ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + examId: string; - @IsNotEmpty() - @ApiProperty({ - description: 'Question exam', - type: String, - example: 'What is this?', - }) - question: string; + @IsNotEmpty() + @ApiProperty({ + description: 'Question exam', + type: String, + example: 'What is this?', + }) + question: string; - @IsNotEmpty() - @ApiProperty({ - description: 'Type question', - type: String, - example: 'Open question', - }) - type: string; + @IsNotEmpty() + @IsEnum(QuestionType) + @ApiProperty({ + description: 'Type question', + type: String, + example: QuestionType.TRUE_FALSE, + enum: QuestionType, + }) + type: QuestionType; - @IsNotEmpty() - @IsInt() - @Min(0) - @ApiProperty({ - description: 'Points in 1 question', - type: Number, - example: 0, - }) - points: number = 0; + @IsNotEmpty() + @IsInt() + @Min(1) + @ApiProperty({ + description: 'Points in 1 question', + type: Number, + example: 1, + }) + points: number = 1; - @IsOptional() - @IsInt() - @Min(1) - @ApiProperty({ - description: 'Order question', - type: Number, - example: 1, - }) - orderIndex?: number; + @IsOptional() + @IsInt() + @Min(1) + @ApiProperty({ + description: 'Order question', + type: Number, + example: 1, + }) + orderIndex?: number; } diff --git a/src/question/dtos/question-response.dto.ts b/src/question/dtos/question-response.dto.ts index e4be207..2b476ff 100644 --- a/src/question/dtos/question-response.dto.ts +++ b/src/question/dtos/question-response.dto.ts @@ -3,6 +3,7 @@ import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response. import { Question } from '../question.entity'; import { Exam } from 'src/exam/exam.entity'; import { ExamResponseDto } from 'src/exam/dtos/exam-response.dto'; +import { QuestionType } from 'src/shared/enums'; export class QuestionResponseDto { @ApiProperty({ @@ -41,9 +42,10 @@ export class QuestionResponseDto { @ApiProperty({ description: 'Type question', type: String, - example: 'Open question', + example: QuestionType.TRUE_FALSE, + enum: QuestionType, }) - type: string; + type: QuestionType; @ApiProperty({ description: 'Points in 1 question', diff --git a/src/question/question.entity.ts b/src/question/question.entity.ts index 70db627..413ae08 100644 --- a/src/question/question.entity.ts +++ b/src/question/question.entity.ts @@ -1,50 +1,60 @@ -import { Exam } from "src/exam/exam.entity"; -import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn, Unique } from "typeorm"; +import { Exam } from 'src/exam/exam.entity'; +import { QuestionType } from 'src/shared/enums'; +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + JoinColumn, + Unique, +} from 'typeorm'; @Entity() -@Unique(["examId", "orderIndex"]) +@Unique(['examId', 'orderIndex']) export class Question { - @PrimaryGeneratedColumn("uuid") - id: string; - - @ManyToOne(() => Exam, (exam) => exam.question, { - onDelete: 'CASCADE', - nullable: false, - }) - @JoinColumn({ name: 'exam_id' }) - exam: Exam; - - @Column({ name: 'exam_id' }) - examId: string; - - @Column({ - nullable: false, - }) - question: string; - - @Column({ - nullable: false, - }) - type: string; - - @Column({ - nullable: false, - default: 0, - }) - points: number; - - @Column({ - nullable: false, - default: 1, - }) - orderIndex: number; - - @CreateDateColumn({ - type: "timestamp with time zone" - }) - createdAt: Date; - - @UpdateDateColumn({ - type: "timestamp with time zone" - }) - updatedAt: Date; -} \ No newline at end of file + @PrimaryGeneratedColumn('uuid') + id: string; + + @ManyToOne(() => Exam, (exam) => exam.question, { + onDelete: 'CASCADE', + nullable: false, + }) + @JoinColumn({ name: 'exam_id' }) + exam: Exam; + + @Column({ name: 'exam_id' }) + examId: string; + + @Column({ + nullable: false, + }) + question: string; + + @Column({ + nullable: false, + }) + type: QuestionType; + + @Column({ + nullable: false, + default: 1, + }) + points: number; + + @Column({ + nullable: false, + default: 1, + }) + orderIndex: number; + + @CreateDateColumn({ + type: 'timestamp with time zone', + }) + createdAt: Date; + + @UpdateDateColumn({ + type: 'timestamp with time zone', + }) + updatedAt: Date; +} diff --git a/src/question/question.service.ts b/src/question/question.service.ts index f25802b..589d80f 100644 --- a/src/question/question.service.ts +++ b/src/question/question.service.ts @@ -143,15 +143,18 @@ export class QuestionService { if (request.user.role === Role.TEACHER) { return { ...baseSearch, - exam: { - courseModule: { - course: { - teacher: { - id: request.user.id, + exam: [ + { + status: ExamStatus.PUBLISHED, + }, + { + courseModule: { + course: { + teacher: { id: request.user.id }, }, }, }, - }, + ], }; } diff --git a/src/shared/enums/index.ts b/src/shared/enums/index.ts index e4a61e3..5e89be5 100644 --- a/src/shared/enums/index.ts +++ b/src/shared/enums/index.ts @@ -2,4 +2,5 @@ export * from './course-level.enum'; export * from './course-status.enum'; export * from './roles.enum'; export * from './environment.enum'; -export * from './exam-status.enum' \ No newline at end of file +export * from './exam-status.enum'; +export * from './question-type.enum'; diff --git a/src/shared/enums/question-type.enum.ts b/src/shared/enums/question-type.enum.ts new file mode 100644 index 0000000..f657d20 --- /dev/null +++ b/src/shared/enums/question-type.enum.ts @@ -0,0 +1,5 @@ +export enum QuestionType { + MULTIPLE_CHOICE = 'multiple_choice', + TRUE_FALSE = 'true_false', + ESSAY = 'essay', +} From f887811f00240eb92e14baa0d124643a69c1520c Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sat, 16 Nov 2024 16:23:29 +0700 Subject: [PATCH 048/155] feat: teacher can find exam published --- src/exam/exam.service.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts index 3e21d43..2d57b51 100644 --- a/src/exam/exam.service.ts +++ b/src/exam/exam.service.ts @@ -53,7 +53,7 @@ export class ExamService { private validateAndCreateCondition( request: AuthenticatedRequest, search: string, - ): FindOptionsWhere { + ): FindOptionsWhere | FindOptionsWhere[] { const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; if (request.user.role === Role.STUDENT) { @@ -61,16 +61,22 @@ export class ExamService { } if (request.user.role === Role.TEACHER) { - return { - ...baseSearch, - courseModule: { - course: { - teacher: { - id: request.user.id, + return [ + { + ...baseSearch, + courseModule: { + course: { + teacher: { + id: request.user.id, + }, }, }, }, - }; + { + ...baseSearch, + status: ExamStatus.PUBLISHED, + }, + ]; } if (request.user.role === Role.ADMIN) { From fde04c87cbad1b9e8d2a00a163f0df21e4c451de Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sat, 16 Nov 2024 16:31:04 +0700 Subject: [PATCH 049/155] fix: fix duplicate import --- src/app.module.ts | 2 - src/user/user.entity.ts | 96 +++++++++++++++++------------------------ 2 files changed, 39 insertions(+), 59 deletions(-) diff --git a/src/app.module.ts b/src/app.module.ts index 17fc55d..2e41704 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -23,8 +23,6 @@ import { UserStreak } from './user-streak/user-streak.entity'; import { UserStreakModule } from './user-streak/user-streak.module'; import { User } from './user/user.entity'; import { UserModule } from './user/user.module'; -import { FileModule } from './file/file.module'; -import { ExamModule } from './exam/exam.module'; const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index ca3f898..61cb8a5 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -2,84 +2,66 @@ import { Course } from 'src/course/course.entity'; import { Enrollment } from 'src/enrollment/enrollment.entity'; import { Role } from 'src/shared/enums/roles.enum'; import { -<<<<<<< HEAD Column, CreateDateColumn, Entity, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn, -======= - Column, - PrimaryGeneratedColumn, - CreateDateColumn, - UpdateDateColumn, ->>>>>>> 95ec4da3bae6ba83f175becb60440f6d278b863e } from 'typeorm'; @Entity() export class User { - @PrimaryGeneratedColumn('uuid') - id: string; + @PrimaryGeneratedColumn('uuid') + id: string; - @Column({ - nullable: false, - unique: true, - }) - username: string; - - @Column({ - nullable: false, - }) - fullname: string; + @Column({ + nullable: false, + unique: true, + }) + username: string; - @Column({ - type: 'enum', - enum: Role, - nullable: false, - default: Role.STUDENT, - }) - role: Role; + @Column({ + nullable: false, + }) + fullname: string; - @Column({ - nullable: false, - unique: true, - }) - password: string; + @Column({ + type: 'enum', + enum: Role, + nullable: false, + default: Role.STUDENT, + }) + role: Role; - @Column({ - nullable: false, - unique: true, - }) - email: string; + @Column({ + nullable: false, + unique: true, + }) + password: string; - @OneToMany(() => Course, (course) => course.teacher) - courses: Course[]; + @Column({ + nullable: false, + unique: true, + }) + email: string; -<<<<<<< HEAD - @OneToMany(() => Enrollment, (enrollment) => enrollment.user) - enrollments: Enrollment[]; + @OneToMany(() => Course, (course) => course.teacher) + courses: Course[]; @CreateDateColumn({ type: 'timestamp with time zone', nullable: false, }) createdAt: Date; -======= - @CreateDateColumn({ - type: 'timestamp with time zone', - nullable: false, - }) - createdAt: Date; ->>>>>>> 95ec4da3bae6ba83f175becb60440f6d278b863e - @UpdateDateColumn({ - type: 'timestamp with time zone', - nullable: false, - }) - updatedAt: Date; + @UpdateDateColumn({ + type: 'timestamp with time zone', + nullable: false, + }) + updatedAt: Date; - @Column({ - nullable: true, - }) - profileKey: string; + @Column({ + nullable: true, + }) + profileKey: string; } From 1e8a4b1294e5955aa2ec9dbce5c5db3eee13d0b9 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sat, 16 Nov 2024 16:38:42 +0700 Subject: [PATCH 050/155] fix: add module depand main --- src/app.module.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app.module.ts b/src/app.module.ts index 3a4c335..ade530a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -58,6 +58,7 @@ const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); ChapterModule, FileModule, ExamModule, + EnrollmentModule, QuestionModule, ], controllers: [AppController], From 5675c358bc9bf0cd90df5ff43c6fd1cfdd9268d2 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sat, 16 Nov 2024 16:50:22 +0700 Subject: [PATCH 051/155] fix: fix bug --- src/user/user.entity.ts | 96 +++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 57 deletions(-) diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index ca3f898..61cb8a5 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -2,84 +2,66 @@ import { Course } from 'src/course/course.entity'; import { Enrollment } from 'src/enrollment/enrollment.entity'; import { Role } from 'src/shared/enums/roles.enum'; import { -<<<<<<< HEAD Column, CreateDateColumn, Entity, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn, -======= - Column, - PrimaryGeneratedColumn, - CreateDateColumn, - UpdateDateColumn, ->>>>>>> 95ec4da3bae6ba83f175becb60440f6d278b863e } from 'typeorm'; @Entity() export class User { - @PrimaryGeneratedColumn('uuid') - id: string; + @PrimaryGeneratedColumn('uuid') + id: string; - @Column({ - nullable: false, - unique: true, - }) - username: string; - - @Column({ - nullable: false, - }) - fullname: string; + @Column({ + nullable: false, + unique: true, + }) + username: string; - @Column({ - type: 'enum', - enum: Role, - nullable: false, - default: Role.STUDENT, - }) - role: Role; + @Column({ + nullable: false, + }) + fullname: string; - @Column({ - nullable: false, - unique: true, - }) - password: string; + @Column({ + type: 'enum', + enum: Role, + nullable: false, + default: Role.STUDENT, + }) + role: Role; - @Column({ - nullable: false, - unique: true, - }) - email: string; + @Column({ + nullable: false, + unique: true, + }) + password: string; - @OneToMany(() => Course, (course) => course.teacher) - courses: Course[]; + @Column({ + nullable: false, + unique: true, + }) + email: string; -<<<<<<< HEAD - @OneToMany(() => Enrollment, (enrollment) => enrollment.user) - enrollments: Enrollment[]; + @OneToMany(() => Course, (course) => course.teacher) + courses: Course[]; @CreateDateColumn({ type: 'timestamp with time zone', nullable: false, }) createdAt: Date; -======= - @CreateDateColumn({ - type: 'timestamp with time zone', - nullable: false, - }) - createdAt: Date; ->>>>>>> 95ec4da3bae6ba83f175becb60440f6d278b863e - @UpdateDateColumn({ - type: 'timestamp with time zone', - nullable: false, - }) - updatedAt: Date; + @UpdateDateColumn({ + type: 'timestamp with time zone', + nullable: false, + }) + updatedAt: Date; - @Column({ - nullable: true, - }) - profileKey: string; + @Column({ + nullable: true, + }) + profileKey: string; } From 6033fd177719c5f41c6c0a1ffbaa022db83792c0 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Sat, 16 Nov 2024 19:06:52 +0700 Subject: [PATCH 052/155] feat: add chat message module with entity, DTOs, and enums; integrate with chat room module --- src/app.module.ts | 2 + src/chat-message/chat-message.controller.ts | 104 ++++++++++++++++++ src/chat-message/chat-message.entity.ts | 54 +++++++++ src/chat-message/chat-message.module.ts | 20 ++++ src/chat-message/chat-message.providers.ts | 10 ++ src/chat-message/chat-message.service.ts | 85 ++++++++++++++ .../dtos/create-chat-message.dto.ts | 43 ++++++++ src/chat-message/dtos/index.ts | 4 + .../paginated-chat-message-response.dto.ts | 74 +++++++++++++ .../dtos/update-chat-message.dto.ts | 24 ++++ .../enums/chat-message-type.enum.ts | 5 + src/chat-room/chat-room.controller.ts | 2 +- src/chat-room/chat-room.service.ts | 5 +- src/chat-room/dtos/index.ts | 2 +- ...ts => paginated-chat-room-response.dto.ts} | 0 15 files changed, 431 insertions(+), 3 deletions(-) create mode 100644 src/chat-message/chat-message.controller.ts create mode 100644 src/chat-message/chat-message.entity.ts create mode 100644 src/chat-message/chat-message.module.ts create mode 100644 src/chat-message/chat-message.providers.ts create mode 100644 src/chat-message/chat-message.service.ts create mode 100644 src/chat-message/dtos/create-chat-message.dto.ts create mode 100644 src/chat-message/dtos/index.ts create mode 100644 src/chat-message/dtos/paginated-chat-message-response.dto.ts create mode 100644 src/chat-message/dtos/update-chat-message.dto.ts create mode 100644 src/chat-message/enums/chat-message-type.enum.ts rename src/chat-room/dtos/{paginated-user-response.dto.ts => paginated-chat-room-response.dto.ts} (100%) diff --git a/src/app.module.ts b/src/app.module.ts index 79c255d..8e9e91d 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -22,6 +22,7 @@ import { RolesGuard } from './shared/guards/role.guard'; import { UserStreakModule } from './user-streak/user-streak.module'; import { UserModule } from './user/user.module'; import { ChatRoomModule } from './chat-room/chat-room.module'; +import { ChatMessageModule } from './chat-message/chat-message.module'; @Module({ imports: [ @@ -55,6 +56,7 @@ import { ChatRoomModule } from './chat-room/chat-room.module'; ExamModule, EnrollmentModule, ChatRoomModule, + ChatMessageModule, ], controllers: [AppController], providers: [ diff --git a/src/chat-message/chat-message.controller.ts b/src/chat-message/chat-message.controller.ts new file mode 100644 index 0000000..cd97bfe --- /dev/null +++ b/src/chat-message/chat-message.controller.ts @@ -0,0 +1,104 @@ +import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Injectable, Param, ParseUUIDPipe, Patch, Post, Query, Req } from "@nestjs/common"; +import { ChatMessageService } from "./chat-message.service"; +import { ApiTags, ApiBearerAuth, ApiResponse } from "@nestjs/swagger"; +import { PaginateQueryDto } from "src/shared/pagination/dtos/paginate-query.dto"; +import { Roles } from "src/shared/decorators/role.decorator"; +import { Role } from "src/shared/enums"; +import { CreateChatMessageDto, ChatMessageResponseDto } from "./dtos"; +import { AuthenticatedRequest } from "src/auth/interfaces/authenticated-request.interface"; + +@Controller('chat-message') +@Injectable() +@ApiTags('Chat Message') +@ApiBearerAuth() +export class ChatMessageController { + constructor( + private readonly chatMessageService: ChatMessageService, + ) { } + + @Get() + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all chat messages', + type: ChatMessageResponseDto, + isArray: true, + }) + @Roles(Role.ADMIN) + async findAll( + @Query() query: PaginateQueryDto, + @Req() request: AuthenticatedRequest, + ) { + return await this.chatMessageService.findAll({ userId: request.user.id, ...query }); + } + + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns a chat message by id', + type: ChatMessageResponseDto, + }) + @Roles(Role.ADMIN) + async findOne( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }) + ) id: string, + ) { + return await this.chatMessageService.findOne({ where: { id } }); + } + + @Post() + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Creates a chat message', + type: ChatMessageResponseDto, + }) + @Roles(Role.ADMIN) + @HttpCode(HttpStatus.CREATED) + async create( + @Body() createChatMessageDto: CreateChatMessageDto, + @Req() request: AuthenticatedRequest + ) { + return await this.chatMessageService.create(request.user.id, createChatMessageDto); + } + + @Patch(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Updates a chat message by id', + type: ChatMessageResponseDto, + }) + async update( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }) + ) id: string, + @Body() updateChatMessageDto: CreateChatMessageDto, + ) { + return await this.chatMessageService.update({ id }, updateChatMessageDto); + } + + @Delete(':id') + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Deletes a chat message by id', + }) + @HttpCode(HttpStatus.NO_CONTENT) + async delete( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }) + ) id: string, + ) { + await this.chatMessageService.delete({ id }); + } +} \ No newline at end of file diff --git a/src/chat-message/chat-message.entity.ts b/src/chat-message/chat-message.entity.ts new file mode 100644 index 0000000..49e8a5c --- /dev/null +++ b/src/chat-message/chat-message.entity.ts @@ -0,0 +1,54 @@ +import { + Entity, + PrimaryGeneratedColumn, + Column, + OneToOne, + ManyToOne, + JoinColumn +} from "typeorm"; +import { ChatMessageType } from "./enums/chat-message-type.enum"; +import { ChatRoom } from "src/chat-room/chat-room.entity"; +import { User } from "src/user/user.entity"; + +@Entity() +export class ChatMessage { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ + nullable: false, + }) + content: string; + + @OneToOne(() => ChatMessage, { + nullable: true, + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'reply' }) + reply?: ChatMessage; + + @Column({ + nullable: false, + default: false, + }) + isEdited: boolean; + + @Column({ + type: 'enum', + enum: ChatMessageType, + nullable: false, + }) + type: ChatMessageType; + + @ManyToOne(() => ChatRoom, { + nullable: false, + onDelete: 'CASCADE', + }) + chatRoom: ChatRoom; + + @ManyToOne(() => User, { + nullable: false, + onDelete: 'CASCADE', + }) + user: User; +} \ No newline at end of file diff --git a/src/chat-message/chat-message.module.ts b/src/chat-message/chat-message.module.ts new file mode 100644 index 0000000..43ef48a --- /dev/null +++ b/src/chat-message/chat-message.module.ts @@ -0,0 +1,20 @@ +import { Module } from "@nestjs/common"; +import { DatabaseModule } from "src/database/database.module"; +import { TypeOrmModule } from "@nestjs/typeorm"; +import { ChatMessage } from "./chat-message.entity"; +import { ChatMessageController } from "./chat-message.controller"; +import { ChatMessageService } from "./chat-message.service"; +import { chatMessageProviders } from "./chat-message.providers"; + +@Module({ + imports: [ + DatabaseModule, + TypeOrmModule.forFeature([ChatMessage]), + ], + controllers: [ChatMessageController], + providers: [ + ...chatMessageProviders, + ChatMessageService, + ], +}) +export class ChatMessageModule { } \ No newline at end of file diff --git a/src/chat-message/chat-message.providers.ts b/src/chat-message/chat-message.providers.ts new file mode 100644 index 0000000..0c53849 --- /dev/null +++ b/src/chat-message/chat-message.providers.ts @@ -0,0 +1,10 @@ +import { DataSource } from 'typeorm'; +import { ChatMessage } from './chat-message.entity'; + +export const chatMessageProviders = [ + { + provide: 'ChatMessageRepository', + useFactory: (dataSource: DataSource) => dataSource.getRepository(ChatMessage), + inject: ['DataSource'], + }, +]; diff --git a/src/chat-message/chat-message.service.ts b/src/chat-message/chat-message.service.ts new file mode 100644 index 0000000..fbe73eb --- /dev/null +++ b/src/chat-message/chat-message.service.ts @@ -0,0 +1,85 @@ +import { Injectable, Inject, InternalServerErrorException, NotFoundException } from '@nestjs/common'; +import { Repository, FindOptionsWhere, FindOneOptions } from 'typeorm'; +import { ChatMessage } from './chat-message.entity'; +import { createPagination } from 'src/shared/pagination'; +import { UpdateChatMessageDto, CreateChatMessageDto, PaginatedChatMessageResponseDto } from './dtos'; + +@Injectable() +export class ChatMessageService { + constructor( + @Inject('ChatMessageRepository') + private readonly chatMessageRepository: Repository, + ) { } + + async create( + userId: string, + createChatMessageDto: CreateChatMessageDto, + ): Promise { + try { + return await this.chatMessageRepository.save({ + ...createChatMessageDto, + user: { id: userId }, + chatRoomId: { id: createChatMessageDto.chatRoomId }, + reply: createChatMessageDto.replyId + ? { id: createChatMessageDto.replyId } + : null, + }); + } catch (error) { + if (error instanceof Error) throw new InternalServerErrorException(error.message); + } + } + + async findAll({ + userId, + page = 1, + limit = 20, + search = '', + }: { + userId: string; + page?: number; + limit?: number; + search?: string; + }): Promise { + const { find } = await createPagination(this.chatMessageRepository, { + page, + limit, + }); + const chatMessages = await find({ + where: { content: search, user: { id: userId } }, + }).run(); + return new PaginatedChatMessageResponseDto( + chatMessages.data, + chatMessages.meta.total, + chatMessages.meta.pageSize, + chatMessages.meta.currentPage, + ); + } + + async findOne(options: FindOneOptions): Promise { + try { + return await this.chatMessageRepository.findOne(options); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); + } + } + + async update(criteria: FindOptionsWhere, updateChatMessageDto: UpdateChatMessageDto): Promise { + try { + await this.chatMessageRepository.update(criteria, { + ...updateChatMessageDto, + isEdited: true, + }); + return await this.findOne({ where: criteria }); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); + } + } + + async delete(criteria: FindOptionsWhere): Promise { + try { + await this.chatMessageRepository.delete(criteria); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); + } + } +} diff --git a/src/chat-message/dtos/create-chat-message.dto.ts b/src/chat-message/dtos/create-chat-message.dto.ts new file mode 100644 index 0000000..cdf7fa1 --- /dev/null +++ b/src/chat-message/dtos/create-chat-message.dto.ts @@ -0,0 +1,43 @@ +import { IsString, IsNotEmpty, IsOptional, IsUUID, IsEnum } from 'class-validator'; +import { ChatMessageType } from '../enums/chat-message-type.enum'; +import { ApiProperty } from '@nestjs/swagger'; + +export class CreateChatMessageDto { + @IsString() + @IsNotEmpty() + @ApiProperty({ + description: 'ChatMessage content', + type: String, + example: 'Hello World!', + }) + content: string; + + @IsOptional() + @IsUUID('4') + @ApiProperty({ + description: 'ChatMessage reply ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + required: false, + }) + replyId?: string; + + @IsEnum(ChatMessageType) + @IsNotEmpty() + @ApiProperty({ + description: 'ChatMessage type', + type: String, + example: ChatMessageType.TEXT, + enum: ChatMessageType, + }) + type: ChatMessageType; + + @IsNotEmpty() + @IsUUID('4') + @ApiProperty({ + description: 'ChatRoom ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + chatRoomId: string; +} \ No newline at end of file diff --git a/src/chat-message/dtos/index.ts b/src/chat-message/dtos/index.ts new file mode 100644 index 0000000..5f187a6 --- /dev/null +++ b/src/chat-message/dtos/index.ts @@ -0,0 +1,4 @@ +export * from './create-chat-message.dto'; +export * from './paginated-chat-message-response.dto'; +export * from './update-chat-message.dto'; +export * from './create-chat-message.dto'; \ No newline at end of file diff --git a/src/chat-message/dtos/paginated-chat-message-response.dto.ts b/src/chat-message/dtos/paginated-chat-message-response.dto.ts new file mode 100644 index 0000000..43075f5 --- /dev/null +++ b/src/chat-message/dtos/paginated-chat-message-response.dto.ts @@ -0,0 +1,74 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { ChatMessage } from '../chat-message.entity'; +import { ChatMessageType } from '../enums/chat-message-type.enum'; +import { UserResponseDto } from 'src/user/dtos/user-response.dto'; + +export class ChatMessageResponseDto { + @ApiProperty({ + description: 'ChatMessage ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + id: string; + + @ApiProperty({ + description: 'ChatMessage content', + type: String, + example: 'Hello World!', + }) + content: string; + + @ApiProperty({ + description: 'ChatMessage type', + type: String, + example: ChatMessageType.TEXT, + enum: ChatMessageType, + }) + type: ChatMessageType; + + @ApiProperty({ + description: 'ChatMessage user', + type: UserResponseDto, + }) + user: UserResponseDto; + + @ApiProperty({ + description: 'ChatRoom ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + chatRoomId: string; + + @ApiProperty({ + description: 'ChatMessage reply', + type: ChatMessageResponseDto, + required: false, + example: ChatMessageResponseDto, + }) + reply?: ChatMessageResponseDto; + + + constructor(chatMessage: ChatMessage) { + this.id = chatMessage.id; + this.content = chatMessage.content; + this.type = chatMessage.type; + this.user = new UserResponseDto(chatMessage.user); + this.chatRoomId = chatMessage.chatRoom.id; + this.reply = chatMessage.reply ? new ChatMessageResponseDto(chatMessage.reply) : null; + } +} + +export class PaginatedChatMessageResponseDto extends PaginatedResponse( + ChatMessageResponseDto, +) { + constructor( + chatMessages: ChatMessage[], + total: number, + pageSize: number, + currentPage: number, + ) { + const chatMessageDtos = chatMessages.map((chatMessage) => new ChatMessageResponseDto(chatMessage)); + super(chatMessageDtos, total, pageSize, currentPage); + } +} diff --git a/src/chat-message/dtos/update-chat-message.dto.ts b/src/chat-message/dtos/update-chat-message.dto.ts new file mode 100644 index 0000000..c8e9f94 --- /dev/null +++ b/src/chat-message/dtos/update-chat-message.dto.ts @@ -0,0 +1,24 @@ +import { IsString, IsNotEmpty, IsOptional, IsUUID, IsEnum } from 'class-validator'; +import { ChatMessageType } from '../enums/chat-message-type.enum'; +import { ApiProperty } from '@nestjs/swagger'; + +export class UpdateChatMessageDto { + @IsString() + @IsNotEmpty() + @ApiProperty({ + description: 'ChatMessage content', + type: String, + example: 'Hello World!', + }) + content: string; + + @IsEnum(ChatMessageType) + @IsNotEmpty() + @ApiProperty({ + description: 'ChatMessage type', + type: String, + example: ChatMessageType.TEXT, + enum: ChatMessageType, + }) + type: ChatMessageType; +} \ No newline at end of file diff --git a/src/chat-message/enums/chat-message-type.enum.ts b/src/chat-message/enums/chat-message-type.enum.ts new file mode 100644 index 0000000..a0e521c --- /dev/null +++ b/src/chat-message/enums/chat-message-type.enum.ts @@ -0,0 +1,5 @@ +export enum ChatMessageType { + TEXT = 'TEXT', + CODE = 'CODE', + IMAGE = 'IMAGE', +} \ No newline at end of file diff --git a/src/chat-room/chat-room.controller.ts b/src/chat-room/chat-room.controller.ts index cf15eae..0761ffa 100644 --- a/src/chat-room/chat-room.controller.ts +++ b/src/chat-room/chat-room.controller.ts @@ -3,7 +3,7 @@ import { Get } from "@nestjs/common"; import { ChatRoomService } from "./chat-room.service"; import { PaginateQueryDto } from "src/shared/pagination/dtos/paginate-query.dto"; import { ApiTags, ApiBearerAuth, ApiResponse, ApiQuery, ApiParam } from "@nestjs/swagger"; -import { ChatRoomResponseDto, PaginatedChatRoomResponseDto } from "./dtos/paginated-user-response.dto"; +import { ChatRoomResponseDto, PaginatedChatRoomResponseDto } from "./dtos/paginated-chat-room-response.dto"; import { Roles } from "src/shared/decorators/role.decorator"; import { Role } from "src/shared/enums"; import { CreateChatRoomDto } from "./dtos/create-chat-room.dto"; diff --git a/src/chat-room/chat-room.service.ts b/src/chat-room/chat-room.service.ts index 845ebb5..8d10281 100644 --- a/src/chat-room/chat-room.service.ts +++ b/src/chat-room/chat-room.service.ts @@ -22,7 +22,10 @@ export class ChatRoomService { async create(createChatRoomDto: CreateChatRoomDto): Promise { try { - return await this.chatRoomRepository.save(createChatRoomDto); + return await this.chatRoomRepository.save({ + ...createChatRoomDto, + chapter: { id: createChatRoomDto.chapterId } + }); } catch (error) { if (error instanceof Error) throw new InternalServerErrorException(error.message); diff --git a/src/chat-room/dtos/index.ts b/src/chat-room/dtos/index.ts index 2e62389..85959d1 100644 --- a/src/chat-room/dtos/index.ts +++ b/src/chat-room/dtos/index.ts @@ -1,3 +1,3 @@ export * from './create-chat-room.dto'; -export * from './paginated-user-response.dto'; +export * from './paginated-chat-room-response.dto'; export * from './update-chat-room.dto'; \ No newline at end of file diff --git a/src/chat-room/dtos/paginated-user-response.dto.ts b/src/chat-room/dtos/paginated-chat-room-response.dto.ts similarity index 100% rename from src/chat-room/dtos/paginated-user-response.dto.ts rename to src/chat-room/dtos/paginated-chat-room-response.dto.ts From c92d2bcff8245f4d671a9fce31cd8070f0382593 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sat, 16 Nov 2024 20:18:54 +0700 Subject: [PATCH 053/155] feat: create exam-attempt dto and api --- src/app.module.ts | 3 + .../dtos/create-exam-attempt.dto.ts | 41 +++ .../dtos/exam-attempt-response.dto.ts | 116 ++++++++ .../dtos/update-exam-attempt.dto.ts | 25 ++ src/exam-attempt/exam-attempt.controller.ts | 182 ++++++++++++ src/exam-attempt/exam-attempt.entity.ts | 74 +++++ src/exam-attempt/exam-attempt.module.ts | 20 ++ src/exam-attempt/exam-attempt.providers.ts | 11 + src/exam-attempt/exam-attempt.service.dto.ts | 281 ++++++++++++++++++ src/exam/exam.entity.ts | 123 ++++---- src/shared/configs/database.config.ts | 5 +- src/shared/enums/exam-attempt-status.enum.ts | 6 + src/shared/enums/index.ts | 3 +- src/user/user.entity.ts | 6 + 14 files changed, 840 insertions(+), 56 deletions(-) create mode 100644 src/exam-attempt/dtos/create-exam-attempt.dto.ts create mode 100644 src/exam-attempt/dtos/exam-attempt-response.dto.ts create mode 100644 src/exam-attempt/dtos/update-exam-attempt.dto.ts create mode 100644 src/exam-attempt/exam-attempt.controller.ts create mode 100644 src/exam-attempt/exam-attempt.entity.ts create mode 100644 src/exam-attempt/exam-attempt.module.ts create mode 100644 src/exam-attempt/exam-attempt.providers.ts create mode 100644 src/exam-attempt/exam-attempt.service.dto.ts create mode 100644 src/shared/enums/exam-attempt-status.enum.ts diff --git a/src/app.module.ts b/src/app.module.ts index 2e41704..14f6cd9 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -23,6 +23,7 @@ import { UserStreak } from './user-streak/user-streak.entity'; import { UserStreakModule } from './user-streak/user-streak.module'; import { User } from './user/user.entity'; import { UserModule } from './user/user.module'; +import { ExamAttemptModule } from './exam-attempt/exam-attempt.module'; const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); @@ -58,6 +59,8 @@ const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); FileModule, ExamModule, EnrollmentModule, + + ExamAttemptModule, ], controllers: [AppController], providers: [ diff --git a/src/exam-attempt/dtos/create-exam-attempt.dto.ts b/src/exam-attempt/dtos/create-exam-attempt.dto.ts new file mode 100644 index 0000000..172a336 --- /dev/null +++ b/src/exam-attempt/dtos/create-exam-attempt.dto.ts @@ -0,0 +1,41 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsInt, IsNotEmpty, IsOptional, Min } from 'class-validator'; +import { ExamAttemptStatus } from 'src/shared/enums'; + +export class CreateExamAttemptDto { + @IsNotEmpty() + @ApiProperty({ + description: 'Exam ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + examId: string; + + @IsNotEmpty() + @ApiProperty({ + description: 'User ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + userId: string; + + @IsNotEmpty() + @Min(0) + @IsInt() + @ApiProperty({ + description: 'Score', + type: Number, + example: 0, + }) + score: number = 0; + + @IsNotEmpty() + @IsEnum(ExamAttemptStatus) + @ApiProperty({ + description: 'Exam attempt status', + type: String, + example: ExamAttemptStatus.IN_PROGRESS, + enum: ExamAttemptStatus, + }) + status: ExamAttemptStatus; +} diff --git a/src/exam-attempt/dtos/exam-attempt-response.dto.ts b/src/exam-attempt/dtos/exam-attempt-response.dto.ts new file mode 100644 index 0000000..3f6a4f9 --- /dev/null +++ b/src/exam-attempt/dtos/exam-attempt-response.dto.ts @@ -0,0 +1,116 @@ +import { ExamAttemptStatus } from 'src/shared/enums'; +import { ApiProperty } from '@nestjs/swagger'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { ExamResponseDto } from 'src/exam/dtos/exam-response.dto'; +import { UserResponseDto } from 'src/user/dtos/user-response.dto'; +import { ExamAttempt } from '../exam-attempt.entity'; + +export class ExamAttemptResponseDto { + @ApiProperty({ + description: 'Exam Attempy ID', + type: String, + example: '123e4567-e89b-12d3-a456-426614174000', + }) + id: string; + + @ApiProperty({ + description: 'Exam Data', + type: String, + example: { + id: 'ce2fd59a-28ea-4192-bfc6-c2347450ab7e', + title: 'Biology', + description: 'This course is an introduction to biology', + timeLimit: 20, + passingScore: 50, + maxAttempts: 1, + shuffleQuestions: false, + status: 'archived', + }, + }) + exam: ExamResponseDto; + + @ApiProperty({ + description: 'User Data', + type: String, + example: { + id: '389d1065-b898-4249-9d3d-e17e100336a7', + username: 'johndoe', + fullname: 'John Doe', + role: 'student', + email: 'johndoe@gmail.com', + profileKey: null, + }, + }) + user: UserResponseDto; + + @ApiProperty({ + description: 'Score', + type: String, + example: 0, + }) + score: number; + + @ApiProperty({ + description: 'Exam attempt status', + type: String, + example: ExamAttemptStatus.IN_PROGRESS, + enum: ExamAttemptStatus, + }) + status: ExamAttemptStatus; + + @ApiProperty({ + description: 'Exam attempt start at', + type: Date, + example: new Date(), + }) + startedAt: Date; + + @ApiProperty({ + description: 'Exam attempt submit at', + type: Date, + example: new Date(), + }) + submittedAt: Date; + + @ApiProperty({ + description: 'Exam created date', + type: Date, + example: new Date(), + }) + createdAt: Date; + + @ApiProperty({ + description: 'Exam updated date', + type: Date, + example: new Date(), + }) + updatedAt: Date; + + constructor(examAttempt: ExamAttempt) { + this.id = examAttempt.id; + this.exam = examAttempt.exam; + this.user = examAttempt.user; + this.score = examAttempt.score; + this.status = examAttempt.status; + this.startedAt = examAttempt.startedAt; + this.submittedAt = examAttempt.submittedAt; + this.createdAt = examAttempt.createdAt; + this.updatedAt = examAttempt.updatedAt; + } +} + +export class PaginatedExamAttemptResponseDto extends PaginatedResponse( + ExamAttemptResponseDto, +) { + constructor( + examAttempt: ExamAttempt[], + total: number, + pageSize: number, + currentPage: number, + ) { + const examAttemptDtos = examAttempt.map( + (examAttempt) => new ExamAttemptResponseDto(examAttempt), + ); + super(examAttemptDtos, total, pageSize, currentPage); + } +} diff --git a/src/exam-attempt/dtos/update-exam-attempt.dto.ts b/src/exam-attempt/dtos/update-exam-attempt.dto.ts new file mode 100644 index 0000000..52f1b02 --- /dev/null +++ b/src/exam-attempt/dtos/update-exam-attempt.dto.ts @@ -0,0 +1,25 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsInt, IsNotEmpty, Min } from 'class-validator'; +import { ExamAttemptStatus } from 'src/shared/enums'; + +export class UpdateExamAttemptDto { + @IsNotEmpty() + @Min(0) + @IsInt() + @ApiProperty({ + description: 'Score', + type: Number, + example: 0, + }) + score: number = 0; + + @IsNotEmpty() + @IsEnum(ExamAttemptStatus) + @ApiProperty({ + description: 'Exam attempt status', + type: String, + example: ExamAttemptStatus.IN_PROGRESS, + enum: ExamAttemptStatus, + }) + status: ExamAttemptStatus; +} diff --git a/src/exam-attempt/exam-attempt.controller.ts b/src/exam-attempt/exam-attempt.controller.ts new file mode 100644 index 0000000..5eb9caa --- /dev/null +++ b/src/exam-attempt/exam-attempt.controller.ts @@ -0,0 +1,182 @@ +import { + Body, + Controller, + Delete, + Get, + HttpCode, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, +} from '@nestjs/common'; +import { ApiTags, ApiBearerAuth, ApiResponse, ApiQuery } from '@nestjs/swagger'; +import { + ExamAttemptResponseDto, + PaginatedExamAttemptResponseDto, +} from './dtos/exam-attempt-response.dto'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { ExamAttemptService } from './exam-attempt.service.dto'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums'; +import { CreateExamAttemptDto } from './dtos/create-exam-attempt.dto'; +import { UpdateExamAttemptDto } from './dtos/update-exam-attempt.dto'; + +@Controller('examAttempt') +@ApiTags('ExamAttempt') +@ApiBearerAuth() +@Injectable() +export class ExamAttemptController { + constructor(private readonly examAttemptService: ExamAttemptService) {} + + @Get() + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all exam-attempt', + type: PaginatedExamAttemptResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findAll( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return await this.examAttemptService.findAll(request, { + page: query.page, + limit: query.limit, + search: query.search, + }); + } + + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns an exam-attempt', + type: ExamAttemptResponseDto, + }) + async findOne( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const exam = await this.examAttemptService.findOne(request, { + where: { id }, + }); + return new ExamAttemptResponseDto(exam); + } + + @Post() + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Create an exam-attempt', + type: ExamAttemptResponseDto, + }) + @HttpCode(HttpStatus.CREATED) + async createExamAttempt( + @Body() createExamAttemptDto: CreateExamAttemptDto, + ): Promise { + const exam = + await this.examAttemptService.createExamAttempt(createExamAttemptDto); + return new ExamAttemptResponseDto(exam); + } + + @Patch(':id') + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Update an exam-attempt', + type: ExamAttemptResponseDto, + }) + async updateExamAttempt( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @Body() updateExamAttemptDto: UpdateExamAttemptDto, + ): Promise { + const exam = await this.examAttemptService.updateExamAttempt( + request, + id, + updateExamAttemptDto, + ); + return new ExamAttemptResponseDto(exam); + } + + @Patch('/submit/:id') + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Update an exam-attempt', + type: ExamAttemptResponseDto, + }) + async submitExamAttempt( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const exam = await this.examAttemptService.submittedExam(request, id); + return new ExamAttemptResponseDto(exam); + } + + @Delete(':id') + @Roles(Role.TEACHER) + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Delete an exam', + }) + @HttpCode(HttpStatus.NO_CONTENT) + async deleteExamAttempt( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise<{ massage: string }> { + await this.examAttemptService.deleteExamAttempt(request, id); + return { massage: 'Exam-attempt deleted successfully' }; + } +} diff --git a/src/exam-attempt/exam-attempt.entity.ts b/src/exam-attempt/exam-attempt.entity.ts new file mode 100644 index 0000000..01f4435 --- /dev/null +++ b/src/exam-attempt/exam-attempt.entity.ts @@ -0,0 +1,74 @@ +import { Exam } from 'src/exam/exam.entity'; +import { ExamAttemptStatus } from 'src/shared/enums'; +import { User } from 'src/user/user.entity'; +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + Unique, + UpdateDateColumn, +} from 'typeorm'; + +@Entity() +export class ExamAttempt { + @PrimaryGeneratedColumn('uuid') + id: string; + + @ManyToOne(() => Exam, (exam) => exam.examAttempt, { + onDelete: 'CASCADE', + nullable: false, + }) + @JoinColumn({ name: 'exam_id' }) + exam: Exam; + + @Column({ name: 'exam_id' }) + examId: String; + + @ManyToOne(() => User, (user) => user.examAttempt, { + onDelete: 'CASCADE', + nullable: false, + }) + @JoinColumn({ name: 'user_id' }) + user: User; + + @Column({ name: 'user_id' }) + userId: String; + + @Column({ + nullable: false, + default: 0, + type: 'decimal', + }) + score: number; + + @Column({ + enum: ExamAttemptStatus, + nullable: false, + }) + status: ExamAttemptStatus; + + @CreateDateColumn({ + type: 'timestamp with time zone', + nullable: false, + }) + startedAt: Date; + + @Column({ + type: 'timestamp with time zone', + nullable: true, + }) + submittedAt: Date; + + @CreateDateColumn({ + type: 'timestamp with time zone', + }) + createdAt: Date; + + @UpdateDateColumn({ + type: 'timestamp with time zone', + }) + updatedAt: Date; +} diff --git a/src/exam-attempt/exam-attempt.module.ts b/src/exam-attempt/exam-attempt.module.ts new file mode 100644 index 0000000..de0f2eb --- /dev/null +++ b/src/exam-attempt/exam-attempt.module.ts @@ -0,0 +1,20 @@ +import { Module } from '@nestjs/common'; +import { DatabaseModule } from 'src/database/database.module'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ExamAttempt } from './exam-attempt.entity'; +import { ExamAttemptController } from './exam-attempt.controller'; +import { examAttemptProviders } from './exam-attempt.providers'; +import { ExamAttemptService } from './exam-attempt.service.dto'; +import { Exam } from 'src/exam/exam.entity'; +import { User } from 'src/user/user.entity'; + +@Module({ + imports: [ + DatabaseModule, + TypeOrmModule.forFeature([ExamAttempt, Exam, User]), + ], + controllers: [ExamAttemptController], + providers: [...examAttemptProviders, ExamAttemptService], + exports: [ExamAttemptService], +}) +export class ExamAttemptModule {} diff --git a/src/exam-attempt/exam-attempt.providers.ts b/src/exam-attempt/exam-attempt.providers.ts new file mode 100644 index 0000000..4567fc9 --- /dev/null +++ b/src/exam-attempt/exam-attempt.providers.ts @@ -0,0 +1,11 @@ +import { DataSource } from 'typeorm'; +import { ExamAttempt } from './exam-attempt.entity'; + +export const examAttemptProviders = [ + { + provide: 'ExamAttemptRepository', + useFactory: (dataSource: DataSource) => + dataSource.getRepository(ExamAttempt), + inject: ['DataSource'], + }, +]; diff --git a/src/exam-attempt/exam-attempt.service.dto.ts b/src/exam-attempt/exam-attempt.service.dto.ts new file mode 100644 index 0000000..024704e --- /dev/null +++ b/src/exam-attempt/exam-attempt.service.dto.ts @@ -0,0 +1,281 @@ +import { + Injectable, + Inject, + NotFoundException, + ForbiddenException, +} from '@nestjs/common'; +import { + FindOneOptions, + FindOptionsSelect, + FindOptionsWhere, + ILike, + IsNull, + Not, + Repository, +} from 'typeorm'; +import { ExamAttempt } from './exam-attempt.entity'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { PaginatedExamAttemptResponseDto } from './dtos/exam-attempt-response.dto'; +import { createPagination } from 'src/shared/pagination'; +import { ExamAttemptStatus, ExamStatus, Role } from 'src/shared/enums'; +import { CreateExamAttemptDto } from './dtos/create-exam-attempt.dto'; +import { Exam } from 'src/exam/exam.entity'; +import { UpdateExamAttemptDto } from './dtos/update-exam-attempt.dto'; +import { User } from 'src/user/user.entity'; + +@Injectable() +export class ExamAttemptService { + constructor( + @Inject('ExamAttemptRepository') + private readonly examAttemptRepository: Repository, + @Inject('ExamRepository') + private readonly examRepository: Repository, + @Inject('UserRepository') + private readonly userRepository: Repository, + ) {} + + async findAll( + request: AuthenticatedRequest, + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.examAttemptRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateCondition(request, search); + const exam = await find({ + where: whereCondition, + relations: ['exam', 'user'], + select: { + user: this.selectPopulateUser(), + exam: this.selectPopulateExam(), + }, + }).run(); + + return exam; + } + + private validateAndCreateCondition( + request: AuthenticatedRequest, + search: string, + ): FindOptionsWhere | FindOptionsWhere[] { + const baseSearch = search ? { id: ILike(`%${search}%`) } : {}; + + if (request.user.role === Role.ADMIN) { + return { ...baseSearch }; + } + + if (request.user.role === Role.STUDENT) { + return [ + { + ...baseSearch, + submittedAt: Not(IsNull()), + status: ExamAttemptStatus.FAILED, + userId: request.user.id, + exam: { + status: ExamStatus.PUBLISHED, + }, + }, + { + ...baseSearch, + submittedAt: Not(IsNull()), + status: ExamAttemptStatus.PASSED, + userId: request.user.id, + exam: { + status: ExamStatus.PUBLISHED, + }, + }, + ]; + } + + if (request.user.role === Role.TEACHER) { + return { + ...baseSearch, + exam: { + courseModule: { + course: { + teacher: { + id: request.user.id, + }, + }, + }, + }, + }; + } + + return { + ...baseSearch, + exam: { + courseModule: { + course: { + teacher: { + id: request.user.id, + }, + }, + }, + }, + }; + } + + async findOne( + request: AuthenticatedRequest, + options: FindOneOptions = {}, + ): Promise { + const whereCondition = this.validateAndCreateCondition(request, ''); + + const exam = await this.examAttemptRepository.findOne({ + ...options, + where: whereCondition, + relations: ['exam', 'user'], + select: { + user: this.selectPopulateUser(), + exam: this.selectPopulateExam(), + }, + }); + + if (!exam) { + throw new NotFoundException('Exam not found'); + } + + return exam; + } + + async createExamAttempt( + createExamAttemptDto: CreateExamAttemptDto, + ): Promise { + const exam = await this.examRepository.findOne({ + where: { id: createExamAttemptDto.examId }, + select: this.selectPopulateExam(), + }); + if (!exam) { + throw new NotFoundException('Exam not found.'); + } + const user = await this.userRepository.findOne({ + where: { id: createExamAttemptDto.userId }, + select: this.selectPopulateUser(), + }); + if (!user) { + throw new NotFoundException('User not found.'); + } + if (user.role != Role.STUDENT) + throw new ForbiddenException('User is not student.'); + if ( + (await this.countExamAttempts( + createExamAttemptDto.examId, + createExamAttemptDto.userId, + )) >= exam.maxAttempts + ) + throw new ForbiddenException( + "Can't create exam-attempt more than max attempt", + ); + const examAttempt = this.examAttemptRepository.create({ + ...createExamAttemptDto, + exam, + user, + }); + if (!examAttempt) throw new NotFoundException("Can't create exam-attempt"); + await this.examAttemptRepository.save(examAttempt); + return examAttempt; + } + + async updateExamAttempt( + request: AuthenticatedRequest, + id: string, + updateExamAttemptDto: UpdateExamAttemptDto, + ): Promise { + const examAttemptInData = await this.findOne(request, { where: { id } }); + if ( + examAttemptInData.status != ExamAttemptStatus.IN_PROGRESS && + updateExamAttemptDto.status == ExamAttemptStatus.IN_PROGRESS + ) { + throw new ForbiddenException("Can't change status to in progress"); + } + const examAttempt = await this.examAttemptRepository.update( + id, + updateExamAttemptDto, + ); + if (!examAttempt) throw new NotFoundException("Can't update exam-attempt"); + return await this.examAttemptRepository.findOne({ + where: { id }, + relations: ['exam', 'user'], + select: { + user: this.selectPopulateUser(), + exam: this.selectPopulateExam(), + }, + }); + } + + async deleteExamAttempt( + request: AuthenticatedRequest, + id: string, + ): Promise { + try { + if (await this.findOne(request, { where: { id } })) { + await this.examAttemptRepository.delete(id); + } + } catch (error) { + if (error instanceof Error) + throw new NotFoundException('Exam-attempt not found'); + } + } + + async submittedExam( + request: AuthenticatedRequest, + id: string, + ): Promise { + const examAttemptInData = await this.findOne(request, { where: { id } }); + + examAttemptInData.submittedAt = new Date(); + + await this.examAttemptRepository.update(id, examAttemptInData); + + const updatedExamAttempt = await this.findOne(request, { where: { id } }); + + return updatedExamAttempt; + } + + async countExamAttempts(examId: string, userId: string): Promise { + const count = await this.examAttemptRepository + .createQueryBuilder('examAttempt') + .where('examAttempt.examId = :examId AND examAttempt.userId = :userId', { + examId, + userId, + }) + .getCount(); + + return count; + } + + private selectPopulateExam(): FindOptionsSelect { + return { + id: true, + title: true, + description: true, + timeLimit: true, + passingScore: true, + maxAttempts: true, + shuffleQuestions: true, + status: true, + }; + } + + private selectPopulateUser(): FindOptionsSelect { + return { + id: true, + username: true, + fullname: true, + role: true, + email: true, + profileKey: true, + }; + } +} diff --git a/src/exam/exam.entity.ts b/src/exam/exam.entity.ts index a839a1d..3bf3c2b 100644 --- a/src/exam/exam.entity.ts +++ b/src/exam/exam.entity.ts @@ -1,68 +1,83 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany, OneToOne, JoinColumn } from "typeorm"; -import { ExamStatus } from "src/shared/enums"; -import { CourseModule } from "src/course-module/course-module.entity"; +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + OneToMany, + OneToOne, + JoinColumn, +} from 'typeorm'; +import { ExamStatus } from 'src/shared/enums'; +import { CourseModule } from 'src/course-module/course-module.entity'; +import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; @Entity() export class Exam { - @PrimaryGeneratedColumn("uuid") - id: string; + @PrimaryGeneratedColumn('uuid') + id: string; - @OneToOne(() => CourseModule, (courseModule) => courseModule.exam, { - onDelete: 'CASCADE', - nullable: false, - }) - @JoinColumn({ name: 'course_module_id' }) - courseModule: CourseModule; + @OneToOne(() => CourseModule, (courseModule) => courseModule.exam, { + onDelete: 'CASCADE', + nullable: false, + }) + @JoinColumn({ name: 'course_module_id' }) + courseModule: CourseModule; - @Column({ name: 'course_module_id' }) - courseModuleId: string; + @Column({ name: 'course_module_id' }) + courseModuleId: string; - @Column({ - nullable: false, - }) - title: string; + @OneToMany(() => ExamAttempt, (examAttempt) => examAttempt.exam, { + cascade: true, + }) + examAttempt: ExamAttempt; - @Column({ - nullable: true, - }) - description: string; + @Column({ + nullable: false, + }) + title: string; - @Column({ - nullable: false, - default: 20, - }) - timeLimit: number; + @Column({ + nullable: true, + }) + description: string; - @Column({ - nullable: false, - }) - passingScore: number; + @Column({ + nullable: false, + default: 20, + }) + timeLimit: number; - @Column({ - nullable: false, - }) - maxAttempts: number; + @Column({ + nullable: false, + }) + passingScore: number; - @Column({ - nullable: false, - default: false, - }) - shuffleQuestions: boolean; + @Column({ + nullable: false, + }) + maxAttempts: number; - @Column({ - enum: ExamStatus, - nullable: false, - default: ExamStatus.DRAFT, - }) - status: ExamStatus; + @Column({ + nullable: false, + default: false, + }) + shuffleQuestions: boolean; - @CreateDateColumn({ - type: "timestamp with time zone" - }) - createdAt: Date; + @Column({ + enum: ExamStatus, + nullable: false, + default: ExamStatus.DRAFT, + }) + status: ExamStatus; - @UpdateDateColumn({ - type: "timestamp with time zone" - }) - updatedAt: Date; -} \ No newline at end of file + @CreateDateColumn({ + type: 'timestamp with time zone', + }) + createdAt: Date; + + @UpdateDateColumn({ + type: 'timestamp with time zone', + }) + updatedAt: Date; +} diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index 0fa4583..94a9e8d 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -10,6 +10,7 @@ import { User } from 'src/user/user.entity'; import { DataSource, DataSourceOptions } from 'typeorm'; import { GLOBAL_CONFIG } from '../constants/global-config.constant'; import { Exam } from 'src/exam/exam.entity'; +import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; const configService = new ConfigService(); @@ -29,7 +30,9 @@ export const databaseConfig: DataSourceOptions = { CourseModule, Chapter, Enrollment, - Exam + Exam, + + ExamAttempt, ], }; diff --git a/src/shared/enums/exam-attempt-status.enum.ts b/src/shared/enums/exam-attempt-status.enum.ts new file mode 100644 index 0000000..d7416ad --- /dev/null +++ b/src/shared/enums/exam-attempt-status.enum.ts @@ -0,0 +1,6 @@ +export enum ExamAttemptStatus { + IN_PROGRESS = 'in_progress', + COMPLETED = 'completed', + FAILED = 'failed', + PASSED = 'passed', +} diff --git a/src/shared/enums/index.ts b/src/shared/enums/index.ts index e4a61e3..5cfa627 100644 --- a/src/shared/enums/index.ts +++ b/src/shared/enums/index.ts @@ -2,4 +2,5 @@ export * from './course-level.enum'; export * from './course-status.enum'; export * from './roles.enum'; export * from './environment.enum'; -export * from './exam-status.enum' \ No newline at end of file +export * from './exam-status.enum'; +export * from './exam-attempt-status.enum'; diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index 61cb8a5..1e55b88 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -1,5 +1,6 @@ import { Course } from 'src/course/course.entity'; import { Enrollment } from 'src/enrollment/enrollment.entity'; +import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; import { Role } from 'src/shared/enums/roles.enum'; import { Column, @@ -48,6 +49,11 @@ export class User { @OneToMany(() => Course, (course) => course.teacher) courses: Course[]; + @OneToMany(() => ExamAttempt, (examAttempt) => examAttempt.exam, { + cascade: true, + }) + examAttempt: ExamAttempt; + @CreateDateColumn({ type: 'timestamp with time zone', nullable: false, From 7a26920c8f4071d2bbe794347e6ac9a72a7349e7 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sat, 16 Nov 2024 21:12:03 +0700 Subject: [PATCH 054/155] feat: add populate --- src/exam/dtos/exam-response.dto.ts | 3 - src/exam/exam.entity.ts | 127 +++++++++++---------- src/exam/exam.service.ts | 20 +++- src/question/dtos/question-response.dto.ts | 2 - src/question/question.service.ts | 55 ++++++++- 5 files changed, 140 insertions(+), 67 deletions(-) diff --git a/src/exam/dtos/exam-response.dto.ts b/src/exam/dtos/exam-response.dto.ts index 416beea..7500666 100644 --- a/src/exam/dtos/exam-response.dto.ts +++ b/src/exam/dtos/exam-response.dto.ts @@ -20,9 +20,6 @@ export class ExamResponseDto { title: 'Thai', description: 'This module is an introduction to programming', orderIndex: 1, - courseId: 'c621f01b-35dc-4448-a065-bd4a6bebb132', - createdAt: new Date(), - updatedAt: new Date(), }, }) courseModule: CourseModuleResponseDto; diff --git a/src/exam/exam.entity.ts b/src/exam/exam.entity.ts index be707b0..8cba313 100644 --- a/src/exam/exam.entity.ts +++ b/src/exam/exam.entity.ts @@ -1,74 +1,83 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, OneToMany, OneToOne, JoinColumn } from "typeorm"; -import { ExamStatus } from "src/shared/enums"; -import { CourseModule } from "src/course-module/course-module.entity"; -import { Question } from "src/question/question.entity"; +import { + Entity, + Column, + PrimaryGeneratedColumn, + CreateDateColumn, + UpdateDateColumn, + OneToMany, + OneToOne, + JoinColumn, +} from 'typeorm'; +import { ExamStatus } from 'src/shared/enums'; +import { CourseModule } from 'src/course-module/course-module.entity'; +import { Question } from 'src/question/question.entity'; @Entity() export class Exam { - @PrimaryGeneratedColumn("uuid") - id: string; + @PrimaryGeneratedColumn('uuid') + id: string; - @OneToOne(() => CourseModule, (courseModule) => courseModule.exam, { - onDelete: 'CASCADE', - nullable: false, - }) - @JoinColumn({ name: 'course_module_id' }) - courseModule: CourseModule; + @OneToOne(() => CourseModule, (courseModule) => courseModule.exam, { + onDelete: 'CASCADE', + nullable: false, + }) + @JoinColumn({ name: 'course_module_id' }) + courseModule: CourseModule; - @Column({ name: 'course_module_id' }) - courseModuleId: string; + @Column({ name: 'course_module_id' }) + courseModuleId: string; - @OneToMany(() => Question, (question) => question.exam, { - cascade: true, - }) - question: Question; + @OneToMany(() => Question, (question) => question.exam, { + cascade: true, + }) + question: Question[]; - @Column({ - nullable: false, - }) - title: string; + @Column({ + nullable: false, + }) + title: string; - @Column({ - nullable: true, - }) - description: string; + @Column({ + nullable: true, + }) + description: string; - @Column({ - nullable: false, - default: 20, - }) - timeLimit: number; + @Column({ + nullable: false, + default: 20, + }) + timeLimit: number; - @Column({ - nullable: false, - }) - passingScore: number; + @Column({ + nullable: false, + }) + passingScore: number; - @Column({ - nullable: false, - }) - maxAttempts: number; + @Column({ + nullable: false, + }) + maxAttempts: number; - @Column({ - nullable: false, - default: false, - }) - shuffleQuestions: boolean; + @Column({ + nullable: false, + default: false, + }) + shuffleQuestions: boolean; - @Column({ - enum: ExamStatus, - nullable: false, - default: ExamStatus.DRAFT, - }) - status: ExamStatus; + @Column({ + enum: ExamStatus, + nullable: false, + default: ExamStatus.DRAFT, + }) + status: ExamStatus; - @CreateDateColumn({ - type: "timestamp with time zone" - }) - createdAt: Date; + @CreateDateColumn({ + type: 'timestamp with time zone', + }) + createdAt: Date; - @UpdateDateColumn({ - type: "timestamp with time zone" - }) - updatedAt: Date; -} \ No newline at end of file + @UpdateDateColumn({ + type: 'timestamp with time zone', + }) + updatedAt: Date; +} diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts index 2d57b51..f342a44 100644 --- a/src/exam/exam.service.ts +++ b/src/exam/exam.service.ts @@ -45,6 +45,9 @@ export class ExamService { const exam = await find({ where: whereCondition, relations: ['courseModule'], + select: { + courseModule: this.selectPopulateCourseModule(), + }, }).run(); return exam; @@ -96,6 +99,9 @@ export class ExamService { ...options, where: whereCondition, relations: ['courseModule'], + select: { + courseModule: this.selectPopulateCourseModule(), + }, }); if (!exam) { @@ -108,6 +114,8 @@ export class ExamService { async createExam(createExamDto: CreateExamDto): Promise { const courseModule = await this.courseModuleRepository.findOne({ where: { id: createExamDto.courseModuleId }, + relations: ['courseModule'], + select: this.selectPopulateCourseModule(), }); const exam = this.examRepository.create({ ...createExamDto, courseModule }); @@ -131,7 +139,13 @@ export class ExamService { } const exam = await this.examRepository.update(id, updateExamDto); if (!exam) throw new NotFoundException("Can't update exam"); - return await this.examRepository.findOne({ where: { id } }); + return await this.examRepository.findOne({ + where: { id }, + relations: ['courseModule'], + select: { + courseModule: this.selectPopulateCourseModule(), + }, + }); } async deleteExam(request: AuthenticatedRequest, id: string): Promise { @@ -143,4 +157,8 @@ export class ExamService { if (error instanceof Error) throw new NotFoundException('Exam not found'); } } + + private selectPopulateCourseModule(): FindOptionsSelect { + return { id: true, title: true, description: true, orderIndex: true }; + } } diff --git a/src/question/dtos/question-response.dto.ts b/src/question/dtos/question-response.dto.ts index 2b476ff..8105f41 100644 --- a/src/question/dtos/question-response.dto.ts +++ b/src/question/dtos/question-response.dto.ts @@ -26,8 +26,6 @@ export class QuestionResponseDto { maxAttempts: 1, shuffleQuestions: true, status: 'draft', - createdAt: new Date(), - updatedAt: new Date(), }, }) exam: Exam; diff --git a/src/question/question.service.ts b/src/question/question.service.ts index 589d80f..35f59a3 100644 --- a/src/question/question.service.ts +++ b/src/question/question.service.ts @@ -8,9 +8,9 @@ import { import { Question } from './question.entity'; import { FindOneOptions, + FindOptionsSelect, FindOptionsWhere, ILike, - Raw, Repository, } from 'typeorm'; import { PaginatedQuestionResponseDto } from './dtos/question-response.dto'; @@ -53,6 +53,9 @@ export class QuestionService { }, where: whereCondition, relations: ['exam'], + select: { + exam: this.selectPopulateExam(), + }, }).run(); return question; @@ -68,6 +71,9 @@ export class QuestionService { ...options, where: whereCondition, relations: ['exam'], + select: { + exam: this.selectPopulateExam(), + }, }); if (!question) { @@ -102,12 +108,20 @@ export class QuestionService { where: { id: examId }, }); + if (!exam) { + throw new NotFoundException('Exam not found.'); + } + if (!exam.shuffleQuestions) { const question = await find({ order: { orderIndex: 'ASC', }, where: whereCondition, + relations: ['exam'], + select: { + exam: this.selectPopulateExam(), + }, }).run(); return question; } @@ -122,6 +136,10 @@ export class QuestionService { ...{ __queryBuilder: baseQuery, }, + relations: ['exam'], + select: { + exam: this.selectPopulateExam(), + }, }).run(); } @@ -202,6 +220,7 @@ export class QuestionService { } const exam = await this.examRepository.findOne({ where: { id: createQuestionDto.examId }, + select: this.selectPopulateExam(), }); if (!exam) { throw new NotFoundException('Exam not found.'); @@ -237,7 +256,13 @@ export class QuestionService { updateQuestionDto, ); if (!question) throw new NotFoundException("Can't update question"); - return await this.questionRepository.findOne({ where: { id } }); + return await this.questionRepository.findOne({ + where: { id }, + relations: ['exam'], + select: { + exam: this.selectPopulateExam(), + }, + }); } catch (error) { if (error.code === '23505') { throw new ConflictException( @@ -263,4 +288,30 @@ export class QuestionService { throw new NotFoundException('Question not found'); } } + + private selectPopulateExamForQuery(): string[] { + return [ + 'question.id', + 'question.title', + 'question.description', + 'question.timeLimit', + 'question.passingScore', + 'question.maxAttempts', + 'question.shuffleQuestions', + 'question.status', + ]; + } + + private selectPopulateExam(): FindOptionsSelect { + return { + id: true, + title: true, + description: true, + timeLimit: true, + passingScore: true, + maxAttempts: true, + shuffleQuestions: true, + status: true, + }; + } } From 9c3f107816f8afcee08e96824e96c28d3178a90d Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sat, 16 Nov 2024 21:14:40 +0700 Subject: [PATCH 055/155] feat: add list to onetomany --- src/user/user.entity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index 1e55b88..1df8ea5 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -52,7 +52,7 @@ export class User { @OneToMany(() => ExamAttempt, (examAttempt) => examAttempt.exam, { cascade: true, }) - examAttempt: ExamAttempt; + examAttempt: ExamAttempt[]; @CreateDateColumn({ type: 'timestamp with time zone', From 1647bbdc5d54d82c4102600c8a189d6d4fb99f9e Mon Sep 17 00:00:00 2001 From: khris-xp Date: Sat, 16 Nov 2024 22:11:18 +0700 Subject: [PATCH 056/155] feat: enrollment module service --- pnpm-lock.yaml | 8481 ++++++++---------- src/app.module.ts | 2 + src/chapter/chapter.entity.ts | 6 +- src/enrollment/dtos/create-enrollment.dto.ts | 3 +- src/enrollment/enrollment.entity.ts | 5 + src/enrollment/enrollment.service.ts | 6 +- src/progress/dtos/create-progress.dto.ts | 60 + src/progress/dtos/progress-response.dto.ts | 92 + src/progress/dtos/update-progress.dto.ts | 4 + src/progress/enums/progress-status.enum.ts | 5 + src/progress/progress.controller.ts | 139 + src/progress/progress.entity.ts | 70 + src/progress/progress.module.ts | 12 + src/progress/progress.service.ts | 83 + src/shared/configs/database.config.ts | 6 +- src/user/user.entity.ts | 93 +- 16 files changed, 4329 insertions(+), 4738 deletions(-) create mode 100644 src/progress/dtos/create-progress.dto.ts create mode 100644 src/progress/dtos/progress-response.dto.ts create mode 100644 src/progress/dtos/update-progress.dto.ts create mode 100644 src/progress/enums/progress-status.enum.ts create mode 100644 src/progress/progress.controller.ts create mode 100644 src/progress/progress.entity.ts create mode 100644 src/progress/progress.module.ts create mode 100644 src/progress/progress.service.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f2a5581..d44c209 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,3640 +1,219 @@ -lockfileVersion: '9.0' +lockfileVersion: '6.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false -importers: - - .: - dependencies: - '@aws-sdk/client-s3': - specifier: ^3.693.0 - version: 3.693.0 - '@nestjs/class-validator': - specifier: 0.13.1 - version: 0.13.1 - '@nestjs/common': - specifier: ^10.0.0 - version: 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/config': - specifier: ^3.3.0 - version: 3.3.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(rxjs@7.8.1) - '@nestjs/core': - specifier: ^10.0.0 - version: 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/jwt': - specifier: ^10.2.0 - version: 10.2.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)) - '@nestjs/mapped-types': - specifier: ^2.0.6 - version: 2.0.6(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) - '@nestjs/platform-express': - specifier: ^10.0.0 - version: 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7) - '@nestjs/swagger': - specifier: ^8.0.5 - version: 8.0.5(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) - '@nestjs/typeorm': - specifier: ^10.0.2 - version: 10.0.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3))) - '@smithy/types': - specifier: ^3.7.1 - version: 3.7.1 - argon2: - specifier: ^0.41.1 - version: 0.41.1 - class-transformer: - specifier: ^0.5.1 - version: 0.5.1 - class-validator: - specifier: ^0.14.1 - version: 0.14.1 - dotenv: - specifier: ^16.4.5 - version: 16.4.5 - joi: - specifier: ^17.13.3 - version: 17.13.3 - mysql2: - specifier: ^3.11.4 - version: 3.11.4 - pg: - specifier: ^8.13.1 - version: 8.13.1 - reflect-metadata: - specifier: ^0.2.0 - version: 0.2.2 - rxjs: - specifier: ^7.8.1 - version: 7.8.1 - typeorm: - specifier: ^0.3.20 - version: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) - typeorm-extension: - specifier: ^3.6.3 - version: 3.6.3(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3))) - devDependencies: - '@nestjs/cli': - specifier: ^10.0.0 - version: 10.4.7 - '@nestjs/schematics': - specifier: ^10.0.0 - version: 10.2.3(chokidar@3.6.0)(typescript@5.6.3) - '@nestjs/testing': - specifier: ^10.0.0 - version: 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@nestjs/platform-express@10.4.7) - '@types/express': - specifier: ^5.0.0 - version: 5.0.0 - '@types/jest': - specifier: ^29.5.2 - version: 29.5.14 - '@types/multer': - specifier: ^1.4.12 - version: 1.4.12 - '@types/node': - specifier: ^20.3.1 - version: 20.17.6 - '@types/supertest': - specifier: ^6.0.0 - version: 6.0.2 - '@typescript-eslint/eslint-plugin': - specifier: ^8.0.0 - version: 8.13.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/parser': - specifier: ^8.0.0 - version: 8.13.0(eslint@8.57.1)(typescript@5.6.3) - eslint: - specifier: ^8.0.0 - version: 8.57.1 - eslint-config-prettier: - specifier: ^9.0.0 - version: 9.1.0(eslint@8.57.1) - eslint-plugin-prettier: - specifier: ^5.0.0 - version: 5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.3.3) - jest: - specifier: ^29.5.0 - version: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) - prettier: - specifier: ^3.0.0 - version: 3.3.3 - source-map-support: - specifier: ^0.5.21 - version: 0.5.21 - supertest: - specifier: ^7.0.0 - version: 7.0.0 - ts-jest: - specifier: ^29.1.0 - version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)))(typescript@5.6.3) - ts-loader: - specifier: ^9.4.3 - version: 9.5.1(typescript@5.6.3)(webpack@5.96.1) - ts-node: - specifier: ^10.9.1 - version: 10.9.2(@types/node@20.17.6)(typescript@5.6.3) - tsconfig-paths: - specifier: ^4.2.0 - version: 4.2.0 - typescript: - specifier: ^5.1.3 - version: 5.6.3 - -packages: - - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - - '@angular-devkit/core@17.3.11': - resolution: {integrity: sha512-vTNDYNsLIWpYk2I969LMQFH29GTsLzxNk/0cLw5q56ARF0v5sIWfHYwGTS88jdDqIpuuettcSczbxeA7EuAmqQ==} - engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - peerDependencies: - chokidar: ^3.5.2 - peerDependenciesMeta: - chokidar: - optional: true - - '@angular-devkit/schematics-cli@17.3.11': - resolution: {integrity: sha512-kcOMqp+PHAKkqRad7Zd7PbpqJ0LqLaNZdY1+k66lLWmkEBozgq8v4ASn/puPWf9Bo0HpCiK+EzLf0VHE8Z/y6Q==} - engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - hasBin: true - - '@angular-devkit/schematics@17.3.11': - resolution: {integrity: sha512-I5wviiIqiFwar9Pdk30Lujk8FczEEc18i22A5c6Z9lbmhPQdTroDnEQdsfXjy404wPe8H62s0I15o4pmMGfTYQ==} - engines: {node: ^18.13.0 || >=20.9.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - - '@aws-crypto/crc32@5.2.0': - resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} - engines: {node: '>=16.0.0'} - - '@aws-crypto/crc32c@5.2.0': - resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} - - '@aws-crypto/sha1-browser@5.2.0': - resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} - - '@aws-crypto/sha256-browser@5.2.0': - resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} - - '@aws-crypto/sha256-js@5.2.0': - resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} - engines: {node: '>=16.0.0'} - - '@aws-crypto/supports-web-crypto@5.2.0': - resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} - - '@aws-crypto/util@5.2.0': - resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - - '@aws-sdk/client-s3@3.693.0': - resolution: {integrity: sha512-vgGI2e0Q6pzyhqfrSysi+sk/i+Nl+lMon67oqj/57RcCw9daL1/inpS+ADuwHpiPWkrg+U0bOXnmHjkLeTslJg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/client-sso-oidc@3.693.0': - resolution: {integrity: sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sts': ^3.693.0 - - '@aws-sdk/client-sso@3.693.0': - resolution: {integrity: sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/client-sts@3.693.0': - resolution: {integrity: sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/core@3.693.0': - resolution: {integrity: sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-env@3.693.0': - resolution: {integrity: sha512-hMUZaRSF7+iBKZfBHNLihFs9zvpM1CB8MBOTnTp5NGCVkRYF3SB2LH+Kcippe0ats4qCyB1eEoyQX99rERp2iQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-http@3.693.0': - resolution: {integrity: sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-ini@3.693.0': - resolution: {integrity: sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sts': ^3.693.0 - - '@aws-sdk/credential-provider-node@3.693.0': - resolution: {integrity: sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-process@3.693.0': - resolution: {integrity: sha512-cvxQkrTWHHjeHrPlj7EWXPnFSq8x7vMx+Zn1oTsMpCY445N9KuzjfJTkmNGwU2GT6rSZI9/0MM02aQvl5bBBTQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-sso@3.693.0': - resolution: {integrity: sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-web-identity@3.693.0': - resolution: {integrity: sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sts': ^3.693.0 - - '@aws-sdk/middleware-bucket-endpoint@3.693.0': - resolution: {integrity: sha512-cPIa+lxMYiFRHtxKfNIVSFGO6LSgZCk42pu3d7KGwD6hu6vXRD5B2/DD3rPcEH1zgl2j0Kx1oGAV7SRXKHSFag==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-expect-continue@3.693.0': - resolution: {integrity: sha512-MuK/gsJWpHz6Tv0CqTCS+QNOxLa2RfPh1biVCu/uO3l7kA0TjQ/C+tfgKvLXeH103tuDrOVINK+bt2ENmI3SWg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-flexible-checksums@3.693.0': - resolution: {integrity: sha512-xkS6zjuE11ob93H9t65kHzphXcUMnN2SmIm2wycUPg+hi8Q6DJA6U2p//6oXkrr9oHy1QvwtllRd7SAd63sFKQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-host-header@3.693.0': - resolution: {integrity: sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-location-constraint@3.693.0': - resolution: {integrity: sha512-eDAExTZ9uNIP7vs2JCVCOuWJauGueisBSn+Ovt7UvvuEUp6KOIJqn8oFxWmyUQu2GvbG4OcaTLgbqD95YHTB0Q==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-logger@3.693.0': - resolution: {integrity: sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-recursion-detection@3.693.0': - resolution: {integrity: sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-sdk-s3@3.693.0': - resolution: {integrity: sha512-5A++RBjJ3guyq5pbYs+Oq5hMlA8CK2OWaHx09cxVfhHWl/RoaY8DXrft4gnhoUEBrrubyMw7r9j7RIMLvS58kg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-ssec@3.693.0': - resolution: {integrity: sha512-Ro5vzI7SRgEeuoMk3fKqFjGv6mG4c7VsSCDwnkiasmafQFBTPvUIpgmu2FXMHqW/OthvoiOzpSrlJ9Bwlx2f8A==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-user-agent@3.693.0': - resolution: {integrity: sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/region-config-resolver@3.693.0': - resolution: {integrity: sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/signature-v4-multi-region@3.693.0': - resolution: {integrity: sha512-s7zbbsoVIriTR4ZGaateKuTqz6ddpazAyHvjk7I9kd+NvGNPiuAI18UdbuiiRI6K5HuYKf1ah6mKWFGPG15/kQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/token-providers@3.693.0': - resolution: {integrity: sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sso-oidc': ^3.693.0 - - '@aws-sdk/types@3.692.0': - resolution: {integrity: sha512-RpNvzD7zMEhiKgmlxGzyXaEcg2khvM7wd5sSHVapOcrde1awQSOMGI4zKBQ+wy5TnDfrm170ROz/ERLYtrjPZA==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/util-arn-parser@3.693.0': - resolution: {integrity: sha512-WC8x6ca+NRrtpAH64rWu+ryDZI3HuLwlEr8EU6/dbC/pt+r/zC0PBoC15VEygUaBA+isppCikQpGyEDu0Yj7gQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/util-endpoints@3.693.0': - resolution: {integrity: sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/util-locate-window@3.693.0': - resolution: {integrity: sha512-ttrag6haJLWABhLqtg1Uf+4LgHWIMOVSYL+VYZmAp2v4PUGOwWmWQH0Zk8RM7YuQcLfH/EoR72/Yxz6A4FKcuw==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/util-user-agent-browser@3.693.0': - resolution: {integrity: sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==} - - '@aws-sdk/util-user-agent-node@3.693.0': - resolution: {integrity: sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==} - engines: {node: '>=16.0.0'} - peerDependencies: - aws-crt: '>=1.0.0' - peerDependenciesMeta: - aws-crt: - optional: true - - '@aws-sdk/xml-builder@3.693.0': - resolution: {integrity: sha512-C/rPwJcqnV8VDr2/VtcQnymSpcfEEgH1Jm6V0VmfXNZFv4Qzf1eCS8nsec0gipYgZB+cBBjfXw5dAk6pJ8ubpw==} - engines: {node: '>=16.0.0'} - - '@babel/code-frame@7.26.2': - resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.26.2': - resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.26.0': - resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.26.2': - resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.25.9': - resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.25.9': - resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.26.0': - resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-plugin-utils@7.25.9': - resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.25.9': - resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.25.9': - resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.25.9': - resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.26.0': - resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.26.2': - resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-syntax-async-generators@7.8.4': - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-bigint@7.8.3': - resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-class-properties@7.12.13': - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-class-static-block@7.14.5': - resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-attributes@7.26.0': - resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-meta@7.10.4': - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-json-strings@7.8.3': - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-jsx@7.25.9': - resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-logical-assignment-operators@7.10.4': - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-numeric-separator@7.10.4': - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-object-rest-spread@7.8.3': - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-optional-catch-binding@7.8.3': - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-optional-chaining@7.8.3': - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-private-property-in-object@7.14.5': - resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-top-level-await@7.14.5': - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-typescript@7.25.9': - resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/template@7.25.9': - resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.25.9': - resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.26.0': - resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} - engines: {node: '>=6.9.0'} - - '@bcoe/v8-coverage@0.2.3': - resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - - '@colors/colors@1.5.0': - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} - engines: {node: '>=0.1.90'} - - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - - '@eslint-community/eslint-utils@4.4.1': - resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - - '@eslint-community/regexpp@4.12.1': - resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@eslint/js@8.57.1': - resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@faker-js/faker@8.4.1': - resolution: {integrity: sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'} - - '@hapi/hoek@9.3.0': - resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} - - '@hapi/topo@5.1.0': - resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} - - '@humanwhocodes/config-array@0.13.0': - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@istanbuljs/load-nyc-config@1.1.0': - resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} - engines: {node: '>=8'} - - '@istanbuljs/schema@0.1.3': - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - - '@jest/console@29.7.0': - resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/core@29.7.0': - resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - '@jest/environment@29.7.0': - resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/expect-utils@29.7.0': - resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/expect@29.7.0': - resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/fake-timers@29.7.0': - resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/globals@29.7.0': - resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/reporters@29.7.0': - resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/source-map@29.6.3': - resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/test-result@29.7.0': - resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/test-sequencer@29.7.0': - resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/transform@29.7.0': - resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/types@29.6.3': - resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jridgewell/gen-mapping@0.3.5': - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} - engines: {node: '>=6.0.0'} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - - '@jridgewell/source-map@0.3.6': - resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} - - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - - '@ljharb/through@2.3.13': - resolution: {integrity: sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==} - engines: {node: '>= 0.4'} - - '@lukeed/csprng@1.1.0': - resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} - engines: {node: '>=8'} - - '@microsoft/tsdoc@0.15.0': - resolution: {integrity: sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==} - - '@nestjs/class-validator@0.13.1': - resolution: {integrity: sha512-Wwpg9KuGnkWOFleBPEFdHQKwqZsF/PFWQaeSaXwatkofgqD7N6AV5J30xIVgScDP+IcE+MdPGZJ38vHQ24KxPQ==} - - '@nestjs/cli@10.4.7': - resolution: {integrity: sha512-4wJTtBJsbvjLIzXl+Qj6DYHv4J7abotuXyk7bes5erL79y+KBT61LulL56SqilzmNnHOAVbXcSXOn9S2aWUn6A==} - engines: {node: '>= 16.14'} - hasBin: true - peerDependencies: - '@swc/cli': ^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 - '@swc/core': ^1.3.62 - peerDependenciesMeta: - '@swc/cli': - optional: true - '@swc/core': - optional: true - - '@nestjs/common@10.4.7': - resolution: {integrity: sha512-gIOpjD3Mx8gfYGxYm/RHPcJzqdknNNFCyY+AxzBT3gc5Xvvik1Dn5OxaMGw5EbVfhZgJKVP0n83giUOAlZQe7w==} - peerDependencies: - class-transformer: '*' - class-validator: '*' - reflect-metadata: ^0.1.12 || ^0.2.0 - rxjs: ^7.1.0 - peerDependenciesMeta: - class-transformer: - optional: true - class-validator: - optional: true - - '@nestjs/config@3.3.0': - resolution: {integrity: sha512-pdGTp8m9d0ZCrjTpjkUbZx6gyf2IKf+7zlkrPNMsJzYZ4bFRRTpXrnj+556/5uiI6AfL5mMrJc2u7dB6bvM+VA==} - peerDependencies: - '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 - rxjs: ^7.1.0 - - '@nestjs/core@10.4.7': - resolution: {integrity: sha512-AIpQzW/vGGqSLkKvll1R7uaSNv99AxZI2EFyVJPNGDgFsfXaohfV1Ukl6f+s75Km+6Fj/7aNl80EqzNWQCS8Ig==} - peerDependencies: - '@nestjs/common': ^10.0.0 - '@nestjs/microservices': ^10.0.0 - '@nestjs/platform-express': ^10.0.0 - '@nestjs/websockets': ^10.0.0 - reflect-metadata: ^0.1.12 || ^0.2.0 - rxjs: ^7.1.0 - peerDependenciesMeta: - '@nestjs/microservices': - optional: true - '@nestjs/platform-express': - optional: true - '@nestjs/websockets': - optional: true - - '@nestjs/jwt@10.2.0': - resolution: {integrity: sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==} - peerDependencies: - '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 - - '@nestjs/mapped-types@2.0.6': - resolution: {integrity: sha512-84ze+CPfp1OWdpRi1/lOu59hOhTz38eVzJvRKrg9ykRFwDz+XleKfMsG0gUqNZYFa6v53XYzeD+xItt8uDW7NQ==} - peerDependencies: - '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 - class-transformer: ^0.4.0 || ^0.5.0 - class-validator: ^0.13.0 || ^0.14.0 - reflect-metadata: ^0.1.12 || ^0.2.0 - peerDependenciesMeta: - class-transformer: - optional: true - class-validator: - optional: true - - '@nestjs/platform-express@10.4.7': - resolution: {integrity: sha512-q6XDOxZPTZ9cxALcVuKUlRBk+cVEv6dW2S8p2yVre22kpEQxq53/OI8EseDvzObGb6hepZ8+yBY04qoYqSlXNQ==} - peerDependencies: - '@nestjs/common': ^10.0.0 - '@nestjs/core': ^10.0.0 - - '@nestjs/schematics@10.2.3': - resolution: {integrity: sha512-4e8gxaCk7DhBxVUly2PjYL4xC2ifDFexCqq1/u4TtivLGXotVk0wHdYuPYe1tHTHuR1lsOkRbfOCpkdTnigLVg==} - peerDependencies: - typescript: '>=4.8.2' - - '@nestjs/swagger@8.0.5': - resolution: {integrity: sha512-ZmBdsbQNs3wIN5kCuvAVbz3/ULh3gi814oHTP49uTqAGi1aT0YSatUyncwQOHBOlRT+rwF+TNjoAsZ+twIk/Jw==} - peerDependencies: - '@fastify/static': ^6.0.0 || ^7.0.0 - '@nestjs/common': ^9.0.0 || ^10.0.0 - '@nestjs/core': ^9.0.0 || ^10.0.0 - class-transformer: '*' - class-validator: '*' - reflect-metadata: ^0.1.12 || ^0.2.0 - peerDependenciesMeta: - '@fastify/static': - optional: true - class-transformer: - optional: true - class-validator: - optional: true - - '@nestjs/testing@10.4.7': - resolution: {integrity: sha512-aS3sQ0v4g8cyHDzW3xJv1+8MiFAkxUNXmnau588IFFI/nBIo/kevLNHNPr85keYekkJ/lwNDW72h8UGg8BYd9w==} - peerDependencies: - '@nestjs/common': ^10.0.0 - '@nestjs/core': ^10.0.0 - '@nestjs/microservices': ^10.0.0 - '@nestjs/platform-express': ^10.0.0 - peerDependenciesMeta: - '@nestjs/microservices': - optional: true - '@nestjs/platform-express': - optional: true - - '@nestjs/typeorm@10.0.2': - resolution: {integrity: sha512-H738bJyydK4SQkRCTeh1aFBxoO1E9xdL/HaLGThwrqN95os5mEyAtK7BLADOS+vldP4jDZ2VQPLj4epWwRqCeQ==} - peerDependencies: - '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 - '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 - reflect-metadata: ^0.1.13 || ^0.2.0 - rxjs: ^7.2.0 - typeorm: ^0.3.0 - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@nuxtjs/opencollective@0.3.2': - resolution: {integrity: sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==} - engines: {node: '>=8.0.0', npm: '>=5.0.0'} - hasBin: true - - '@phc/format@1.0.0': - resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==} - engines: {node: '>=10'} - - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - '@pkgr/core@0.1.1': - resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - - '@scarf/scarf@1.4.0': - resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} - - '@sideway/address@4.1.5': - resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} - - '@sideway/formula@3.0.1': - resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} - - '@sideway/pinpoint@2.0.0': - resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} - - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - - '@sinonjs/commons@3.0.1': - resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} - - '@sinonjs/fake-timers@10.3.0': - resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} - - '@smithy/abort-controller@3.1.8': - resolution: {integrity: sha512-+3DOBcUn5/rVjlxGvUPKc416SExarAQ+Qe0bqk30YSUjbepwpS7QN0cyKUSifvLJhdMZ0WPzPP5ymut0oonrpQ==} - engines: {node: '>=16.0.0'} - - '@smithy/chunked-blob-reader-native@3.0.1': - resolution: {integrity: sha512-VEYtPvh5rs/xlyqpm5NRnfYLZn+q0SRPELbvBV+C/G7IQ+ouTuo+NKKa3ShG5OaFR8NYVMXls9hPYLTvIKKDrQ==} - - '@smithy/chunked-blob-reader@4.0.0': - resolution: {integrity: sha512-jSqRnZvkT4egkq/7b6/QRCNXmmYVcHwnJldqJ3IhVpQE2atObVJ137xmGeuGFhjFUr8gCEVAOKwSY79OvpbDaQ==} - - '@smithy/config-resolver@3.0.12': - resolution: {integrity: sha512-YAJP9UJFZRZ8N+UruTeq78zkdjUHmzsY62J4qKWZ4SXB4QXJ/+680EfXXgkYA2xj77ooMqtUY9m406zGNqwivQ==} - engines: {node: '>=16.0.0'} - - '@smithy/core@2.5.3': - resolution: {integrity: sha512-96uW8maifUSmehaeW7uydWn7wBc98NEeNI3zN8vqakGpyCQgzyJaA64Z4FCOUmAdCJkhppd/7SZ798Fo4Xx37g==} - engines: {node: '>=16.0.0'} - - '@smithy/credential-provider-imds@3.2.7': - resolution: {integrity: sha512-cEfbau+rrWF8ylkmmVAObOmjbTIzKyUC5TkBL58SbLywD0RCBC4JAUKbmtSm2w5KUJNRPGgpGFMvE2FKnuNlWQ==} - engines: {node: '>=16.0.0'} - - '@smithy/eventstream-codec@3.1.9': - resolution: {integrity: sha512-F574nX0hhlNOjBnP+noLtsPFqXnWh2L0+nZKCwcu7P7J8k+k+rdIDs+RMnrMwrzhUE4mwMgyN0cYnEn0G8yrnQ==} - - '@smithy/eventstream-serde-browser@3.0.13': - resolution: {integrity: sha512-Nee9m+97o9Qj6/XeLz2g2vANS2SZgAxV4rDBMKGHvFJHU/xz88x2RwCkwsvEwYjSX4BV1NG1JXmxEaDUzZTAtw==} - engines: {node: '>=16.0.0'} - - '@smithy/eventstream-serde-config-resolver@3.0.10': - resolution: {integrity: sha512-K1M0x7P7qbBUKB0UWIL5KOcyi6zqV5mPJoL0/o01HPJr0CSq3A9FYuJC6e11EX6hR8QTIR++DBiGrYveOu6trw==} - engines: {node: '>=16.0.0'} - - '@smithy/eventstream-serde-node@3.0.12': - resolution: {integrity: sha512-kiZymxXvZ4tnuYsPSMUHe+MMfc4FTeFWJIc0Q5wygJoUQM4rVHNghvd48y7ppuulNMbuYt95ah71pYc2+o4JOA==} - engines: {node: '>=16.0.0'} - - '@smithy/eventstream-serde-universal@3.0.12': - resolution: {integrity: sha512-1i8ifhLJrOZ+pEifTlF0EfZzMLUGQggYQ6WmZ4d5g77zEKf7oZ0kvh1yKWHPjofvOwqrkwRDVuxuYC8wVd662A==} - engines: {node: '>=16.0.0'} - - '@smithy/fetch-http-handler@4.1.1': - resolution: {integrity: sha512-bH7QW0+JdX0bPBadXt8GwMof/jz0H28I84hU1Uet9ISpzUqXqRQ3fEZJ+ANPOhzSEczYvANNl3uDQDYArSFDtA==} - - '@smithy/hash-blob-browser@3.1.9': - resolution: {integrity: sha512-wOu78omaUuW5DE+PVWXiRKWRZLecARyP3xcq5SmkXUw9+utgN8HnSnBfrjL2B/4ZxgqPjaAJQkC/+JHf1ITVaQ==} - - '@smithy/hash-node@3.0.10': - resolution: {integrity: sha512-3zWGWCHI+FlJ5WJwx73Mw2llYR8aflVyZN5JhoqLxbdPZi6UyKSdCeXAWJw9ja22m6S6Tzz1KZ+kAaSwvydi0g==} - engines: {node: '>=16.0.0'} - - '@smithy/hash-stream-node@3.1.9': - resolution: {integrity: sha512-3XfHBjSP3oDWxLmlxnt+F+FqXpL3WlXs+XXaB6bV9Wo8BBu87fK1dSEsyH7Z4ZHRmwZ4g9lFMdf08m9hoX1iRA==} - engines: {node: '>=16.0.0'} - - '@smithy/invalid-dependency@3.0.10': - resolution: {integrity: sha512-Lp2L65vFi+cj0vFMu2obpPW69DU+6O5g3086lmI4XcnRCG8PxvpWC7XyaVwJCxsZFzueHjXnrOH/E0pl0zikfA==} - - '@smithy/is-array-buffer@2.2.0': - resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} - engines: {node: '>=14.0.0'} - - '@smithy/is-array-buffer@3.0.0': - resolution: {integrity: sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==} - engines: {node: '>=16.0.0'} - - '@smithy/md5-js@3.0.10': - resolution: {integrity: sha512-m3bv6dApflt3fS2Y1PyWPUtRP7iuBlvikEOGwu0HsCZ0vE7zcIX+dBoh3e+31/rddagw8nj92j0kJg2TfV+SJA==} - - '@smithy/middleware-content-length@3.0.12': - resolution: {integrity: sha512-1mDEXqzM20yywaMDuf5o9ue8OkJ373lSPbaSjyEvkWdqELhFMyNNgKGWL/rCSf4KME8B+HlHKuR8u9kRj8HzEQ==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-endpoint@3.2.3': - resolution: {integrity: sha512-Hdl9296i/EMptaX7agrSzJZDiz5Y8XPUeBbctTmMtnCguGpqfU3jVsTUan0VLaOhsnquqWLL8Bl5HrlbVGT1og==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-retry@3.0.27': - resolution: {integrity: sha512-H3J/PjJpLL7Tt+fxDKiOD25sMc94YetlQhCnYeNmina2LZscAdu0ZEZPas/kwePHABaEtqp7hqa5S4UJgMs1Tg==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-serde@3.0.10': - resolution: {integrity: sha512-MnAuhh+dD14F428ubSJuRnmRsfOpxSzvRhaGVTvd/lrUDE3kxzCCmH8lnVTvoNQnV2BbJ4c15QwZ3UdQBtFNZA==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-stack@3.0.10': - resolution: {integrity: sha512-grCHyoiARDBBGPyw2BeicpjgpsDFWZZxptbVKb3CRd/ZA15F/T6rZjCCuBUjJwdck1nwUuIxYtsS4H9DDpbP5w==} - engines: {node: '>=16.0.0'} - - '@smithy/node-config-provider@3.1.11': - resolution: {integrity: sha512-URq3gT3RpDikh/8MBJUB+QGZzfS7Bm6TQTqoh4CqE8NBuyPkWa5eUXj0XFcFfeZVgg3WMh1u19iaXn8FvvXxZw==} - engines: {node: '>=16.0.0'} - - '@smithy/node-http-handler@3.3.1': - resolution: {integrity: sha512-fr+UAOMGWh6bn4YSEezBCpJn9Ukp9oR4D32sCjCo7U81evE11YePOQ58ogzyfgmjIO79YeOdfXXqr0jyhPQeMg==} - engines: {node: '>=16.0.0'} - - '@smithy/property-provider@3.1.10': - resolution: {integrity: sha512-n1MJZGTorTH2DvyTVj+3wXnd4CzjJxyXeOgnTlgNVFxaaMeT4OteEp4QrzF8p9ee2yg42nvyVK6R/awLCakjeQ==} - engines: {node: '>=16.0.0'} - - '@smithy/protocol-http@4.1.7': - resolution: {integrity: sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==} - engines: {node: '>=16.0.0'} - - '@smithy/querystring-builder@3.0.10': - resolution: {integrity: sha512-nT9CQF3EIJtIUepXQuBFb8dxJi3WVZS3XfuDksxSCSn+/CzZowRLdhDn+2acbBv8R6eaJqPupoI/aRFIImNVPQ==} - engines: {node: '>=16.0.0'} - - '@smithy/querystring-parser@3.0.10': - resolution: {integrity: sha512-Oa0XDcpo9SmjhiDD9ua2UyM3uU01ZTuIrNdZvzwUTykW1PM8o2yJvMh1Do1rY5sUQg4NDV70dMi0JhDx4GyxuQ==} - engines: {node: '>=16.0.0'} - - '@smithy/service-error-classification@3.0.10': - resolution: {integrity: sha512-zHe642KCqDxXLuhs6xmHVgRwy078RfqxP2wRDpIyiF8EmsWXptMwnMwbVa50lw+WOGNrYm9zbaEg0oDe3PTtvQ==} - engines: {node: '>=16.0.0'} - - '@smithy/shared-ini-file-loader@3.1.11': - resolution: {integrity: sha512-AUdrIZHFtUgmfSN4Gq9nHu3IkHMa1YDcN+s061Nfm+6pQ0mJy85YQDB0tZBCmls0Vuj22pLwDPmL92+Hvfwwlg==} - engines: {node: '>=16.0.0'} - - '@smithy/signature-v4@4.2.3': - resolution: {integrity: sha512-pPSQQ2v2vu9vc8iew7sszLd0O09I5TRc5zhY71KA+Ao0xYazIG+uLeHbTJfIWGO3BGVLiXjUr3EEeCcEQLjpWQ==} - engines: {node: '>=16.0.0'} - - '@smithy/smithy-client@3.4.4': - resolution: {integrity: sha512-dPGoJuSZqvirBq+yROapBcHHvFjChoAQT8YPWJ820aPHHiowBlB3RL1Q4kPT1hx0qKgJuf+HhyzKi5Gbof4fNA==} - engines: {node: '>=16.0.0'} - - '@smithy/types@3.7.1': - resolution: {integrity: sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==} - engines: {node: '>=16.0.0'} - - '@smithy/url-parser@3.0.10': - resolution: {integrity: sha512-j90NUalTSBR2NaZTuruEgavSdh8MLirf58LoGSk4AtQfyIymogIhgnGUU2Mga2bkMkpSoC9gxb74xBXL5afKAQ==} - - '@smithy/util-base64@3.0.0': - resolution: {integrity: sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-body-length-browser@3.0.0': - resolution: {integrity: sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==} - - '@smithy/util-body-length-node@3.0.0': - resolution: {integrity: sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==} - engines: {node: '>=16.0.0'} - - '@smithy/util-buffer-from@2.2.0': - resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} - engines: {node: '>=14.0.0'} - - '@smithy/util-buffer-from@3.0.0': - resolution: {integrity: sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==} - engines: {node: '>=16.0.0'} - - '@smithy/util-config-provider@3.0.0': - resolution: {integrity: sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-defaults-mode-browser@3.0.27': - resolution: {integrity: sha512-GV8NvPy1vAGp7u5iD/xNKUxCorE4nQzlyl057qRac+KwpH5zq8wVq6rE3lPPeuFLyQXofPN6JwxL1N9ojGapiQ==} - engines: {node: '>= 10.0.0'} - - '@smithy/util-defaults-mode-node@3.0.27': - resolution: {integrity: sha512-7+4wjWfZqZxZVJvDutO+i1GvL6bgOajEkop4FuR6wudFlqBiqwxw3HoH6M9NgeCd37km8ga8NPp2JacQEtAMPg==} - engines: {node: '>= 10.0.0'} - - '@smithy/util-endpoints@2.1.6': - resolution: {integrity: sha512-mFV1t3ndBh0yZOJgWxO9J/4cHZVn5UG1D8DeCc6/echfNkeEJWu9LD7mgGH5fHrEdR7LDoWw7PQO6QiGpHXhgA==} - engines: {node: '>=16.0.0'} - - '@smithy/util-hex-encoding@3.0.0': - resolution: {integrity: sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-middleware@3.0.10': - resolution: {integrity: sha512-eJO+/+RsrG2RpmY68jZdwQtnfsxjmPxzMlQpnHKjFPwrYqvlcT+fHdT+ZVwcjlWSrByOhGr9Ff2GG17efc192A==} - engines: {node: '>=16.0.0'} - - '@smithy/util-retry@3.0.10': - resolution: {integrity: sha512-1l4qatFp4PiU6j7UsbasUHL2VU023NRB/gfaa1M0rDqVrRN4g3mCArLRyH3OuktApA4ye+yjWQHjdziunw2eWA==} - engines: {node: '>=16.0.0'} - - '@smithy/util-stream@3.3.1': - resolution: {integrity: sha512-Ff68R5lJh2zj+AUTvbAU/4yx+6QPRzg7+pI7M1FbtQHcRIp7xvguxVsQBKyB3fwiOwhAKu0lnNyYBaQfSW6TNw==} - engines: {node: '>=16.0.0'} - - '@smithy/util-uri-escape@3.0.0': - resolution: {integrity: sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==} - engines: {node: '>=16.0.0'} - - '@smithy/util-utf8@2.3.0': - resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} - engines: {node: '>=14.0.0'} - - '@smithy/util-utf8@3.0.0': - resolution: {integrity: sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==} - engines: {node: '>=16.0.0'} - - '@smithy/util-waiter@3.1.9': - resolution: {integrity: sha512-/aMXPANhMOlMPjfPtSrDfPeVP8l56SJlz93xeiLmhLe5xvlXA5T3abZ2ilEsDEPeY9T/wnN/vNGn9wa1SbufWA==} - engines: {node: '>=16.0.0'} - - '@sqltools/formatter@1.2.5': - resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} - - '@tsconfig/node10@1.0.11': - resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} - - '@tsconfig/node12@1.0.11': - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - - '@tsconfig/node14@1.0.3': - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - - '@tsconfig/node16@1.0.4': - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.6.8': - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.20.6': - resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - - '@types/body-parser@1.19.5': - resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} - - '@types/connect@3.4.38': - resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - - '@types/cookiejar@2.1.5': - resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} - - '@types/eslint-scope@3.7.7': - resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} - - '@types/eslint@9.6.1': - resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} - - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - - '@types/express-serve-static-core@5.0.1': - resolution: {integrity: sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==} - - '@types/express@5.0.0': - resolution: {integrity: sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==} - - '@types/graceful-fs@4.1.9': - resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} - - '@types/http-errors@2.0.4': - resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} - - '@types/istanbul-lib-coverage@2.0.6': - resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} - - '@types/istanbul-lib-report@3.0.3': - resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} - - '@types/istanbul-reports@3.0.4': - resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} - - '@types/jest@29.5.14': - resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} - - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - - '@types/jsonwebtoken@9.0.5': - resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==} - - '@types/methods@1.1.4': - resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} - - '@types/mime@1.3.5': - resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} - - '@types/multer@1.4.12': - resolution: {integrity: sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==} - - '@types/node@20.17.6': - resolution: {integrity: sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==} - - '@types/qs@6.9.17': - resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==} - - '@types/range-parser@1.2.7': - resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - - '@types/send@0.17.4': - resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} - - '@types/serve-static@1.15.7': - resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} - - '@types/stack-utils@2.0.3': - resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - - '@types/superagent@8.1.9': - resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==} - - '@types/supertest@6.0.2': - resolution: {integrity: sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==} - - '@types/validator@13.12.2': - resolution: {integrity: sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==} - - '@types/yargs-parser@21.0.3': - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - - '@types/yargs@17.0.33': - resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} - - '@typescript-eslint/eslint-plugin@8.13.0': - resolution: {integrity: sha512-nQtBLiZYMUPkclSeC3id+x4uVd1SGtHuElTxL++SfP47jR0zfkZBJHc+gL4qPsgTuypz0k8Y2GheaDYn6Gy3rg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/parser@8.13.0': - resolution: {integrity: sha512-w0xp+xGg8u/nONcGw1UXAr6cjCPU1w0XVyBs6Zqaj5eLmxkKQAByTdV/uGgNN5tVvN/kKpoQlP2cL7R+ajZZIQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/scope-manager@8.13.0': - resolution: {integrity: sha512-XsGWww0odcUT0gJoBZ1DeulY1+jkaHUciUq4jKNv4cpInbvvrtDoyBH9rE/n2V29wQJPk8iCH1wipra9BhmiMA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/type-utils@8.13.0': - resolution: {integrity: sha512-Rqnn6xXTR316fP4D2pohZenJnp+NwQ1mo7/JM+J1LWZENSLkJI8ID8QNtlvFeb0HnFSK94D6q0cnMX6SbE5/vA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/types@8.13.0': - resolution: {integrity: sha512-4cyFErJetFLckcThRUFdReWJjVsPCqyBlJTi6IDEpc1GWCIIZRFxVppjWLIMcQhNGhdWJJRYFHpHoDWvMlDzng==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.13.0': - resolution: {integrity: sha512-v7SCIGmVsRK2Cy/LTLGN22uea6SaUIlpBcO/gnMGT/7zPtxp90bphcGf4fyrCQl3ZtiBKqVTG32hb668oIYy1g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/utils@8.13.0': - resolution: {integrity: sha512-A1EeYOND6Uv250nybnLZapeXpYMl8tkzYUxqmoKAWnI4sei3ihf2XdZVd+vVOmHGcp3t+P7yRrNsyyiXTvShFQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - - '@typescript-eslint/visitor-keys@8.13.0': - resolution: {integrity: sha512-7N/+lztJqH4Mrf0lb10R/CbI1EaAMMGyF5y0oJvFoAhafwgiRA7TXyd8TFn8FC8k5y2dTsYogg238qavRGNnlw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@ungap/structured-clone@1.2.0': - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - - '@webassemblyjs/ast@1.14.1': - resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} - - '@webassemblyjs/floating-point-hex-parser@1.13.2': - resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} - - '@webassemblyjs/helper-api-error@1.13.2': - resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} - - '@webassemblyjs/helper-buffer@1.14.1': - resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} - - '@webassemblyjs/helper-numbers@1.13.2': - resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} - - '@webassemblyjs/helper-wasm-bytecode@1.13.2': - resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} - - '@webassemblyjs/helper-wasm-section@1.14.1': - resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} - - '@webassemblyjs/ieee754@1.13.2': - resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} - - '@webassemblyjs/leb128@1.13.2': - resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} - - '@webassemblyjs/utf8@1.13.2': - resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} - - '@webassemblyjs/wasm-edit@1.14.1': - resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} - - '@webassemblyjs/wasm-gen@1.14.1': - resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} - - '@webassemblyjs/wasm-opt@1.14.1': - resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} - - '@webassemblyjs/wasm-parser@1.14.1': - resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} - - '@webassemblyjs/wast-printer@1.14.1': - resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} - - '@xtuc/ieee754@1.2.0': - resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} - - '@xtuc/long@4.2.2': - resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} - - accepts@1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} - - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} - engines: {node: '>=0.4.0'} - - acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} - engines: {node: '>=0.4.0'} - hasBin: true - - ajv-formats@2.1.1: - resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - - ajv-keywords@3.5.2: - resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} - peerDependencies: - ajv: ^6.9.1 - - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - - ajv@8.12.0: - resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} - - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.1.0: - resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - app-root-path@3.1.0: - resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} - engines: {node: '>= 6.0.0'} - - append-field@1.0.0: - resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} - - arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - - argon2@0.41.1: - resolution: {integrity: sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==} - engines: {node: '>=16.17.0'} - - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - array-flatten@1.1.1: - resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - - array-timsort@1.0.3: - resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} - - asap@2.0.6: - resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} - - async@3.2.6: - resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} - - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - - aws-ssl-profiles@1.1.2: - resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} - engines: {node: '>= 6.0.0'} - - babel-jest@29.7.0: - resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.8.0 - - babel-plugin-istanbul@6.1.1: - resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} - engines: {node: '>=8'} - - babel-plugin-jest-hoist@29.6.3: - resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - babel-preset-current-node-syntax@1.1.0: - resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} - peerDependencies: - '@babel/core': ^7.0.0 - - babel-preset-jest@29.6.3: - resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.0.0 - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - - body-parser@1.20.3: - resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - - bowser@2.11.0: - resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} - - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - browserslist@4.24.2: - resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - bs-logger@0.2.6: - resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} - engines: {node: '>= 6'} - - bser@2.1.1: - resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} - - buffer-equal-constant-time@1.0.1: - resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - - busboy@1.6.0: - resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} - engines: {node: '>=10.16.0'} - - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - - call-bind@1.0.7: - resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} - engines: {node: '>= 0.4'} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - - caniuse-lite@1.0.30001679: - resolution: {integrity: sha512-j2YqID/YwpLnKzCmBOS4tlZdWprXm3ZmQLBH9ZBXFOhoxLA46fwyBvx6toCBWBmnuwUY/qB3kEU6gFx8qgCroA==} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - chalk@5.3.0: - resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - - char-regex@1.0.2: - resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} - engines: {node: '>=10'} - - chardet@0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - - chrome-trace-event@1.0.4: - resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} - engines: {node: '>=6.0'} - - ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} - - cjs-module-lexer@1.4.1: - resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} - - class-transformer@0.5.1: - resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} - - class-validator@0.14.1: - resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==} - - cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} - - cli-highlight@2.1.11: - resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} - engines: {node: '>=8.0.0', npm: '>=5.0.0'} - hasBin: true - - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} - - cli-table3@0.6.5: - resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} - engines: {node: 10.* || >= 12.*} - - cli-width@3.0.0: - resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} - engines: {node: '>= 10'} - - cli-width@4.1.0: - resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} - engines: {node: '>= 12'} - - cliui@7.0.4: - resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} - - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - - clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} - - co@4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - - collect-v8-coverage@1.0.2: - resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - - commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - - comment-json@4.2.5: - resolution: {integrity: sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==} - engines: {node: '>= 6'} - - component-emitter@1.3.1: - resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - concat-stream@1.6.2: - resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} - engines: {'0': node >= 0.8} - - consola@2.15.3: - resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} - - consola@3.2.3: - resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} - engines: {node: ^14.18.0 || >=16.10.0} - - content-disposition@0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - cookie-signature@1.0.6: - resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - - cookie@0.7.1: - resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} - engines: {node: '>= 0.6'} - - cookiejar@2.1.4: - resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} - - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - - cors@2.8.5: - resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} - engines: {node: '>= 0.10'} - - cosmiconfig@8.3.6: - resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true - - create-jest@29.7.0: - resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - - create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - - cross-spawn@7.0.5: - resolution: {integrity: sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==} - engines: {node: '>= 8'} - - dayjs@1.11.13: - resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} - - debug@2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - debug@4.3.7: - resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - dedent@1.5.3: - resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - - deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - - defaults@1.0.4: - resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} - - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - - denque@2.1.0: - resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} - engines: {node: '>=0.10'} - - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - - destr@2.0.3: - resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} - - destroy@1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - - detect-newline@3.1.0: - resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} - engines: {node: '>=8'} - - dezalgo@1.0.4: - resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} - - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} - - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - - dotenv-expand@10.0.0: - resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} - engines: {node: '>=12'} - - dotenv@16.4.5: - resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} - engines: {node: '>=12'} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - ebec@1.1.1: - resolution: {integrity: sha512-JZ1vcvPQtR+8LGbZmbjG21IxLQq/v47iheJqn2F6yB2CgnGfn8ZVg3myHrf3buIZS8UCwQK0jOSIb3oHX7aH8g==} - - ebec@2.3.0: - resolution: {integrity: sha512-bt+0tSL7223VU3PSVi0vtNLZ8pO1AfWolcPPMk2a/a5H+o/ZU9ky0n3A0zhrR4qzJTN61uPsGIO4ShhOukdzxA==} - - ecdsa-sig-formatter@1.0.11: - resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} - - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - - ejs@3.1.10: - resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} - engines: {node: '>=0.10.0'} - hasBin: true - - electron-to-chromium@1.5.55: - resolution: {integrity: sha512-6maZ2ASDOTBtjt9FhqYPRnbvKU5tjG0IN9SztUOWYw2AzNDNpKJYLJmlK0/En4Hs/aiWnB+JZ+gW19PIGszgKg==} - - emittery@0.13.1: - resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} - engines: {node: '>=12'} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - encodeurl@1.0.2: - resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} - engines: {node: '>= 0.8'} - - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} - - enhanced-resolve@5.17.1: - resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} - engines: {node: '>=10.13.0'} - - envix@1.5.0: - resolution: {integrity: sha512-IOxTKT+tffjxgvX2O5nq6enbkv6kBQ/QdMy18bZWo0P0rKPvsRp2/EypIPwTvJfnmk3VdOlq/KcRSZCswefM/w==} - engines: {node: '>=18.0.0'} - - error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - - es-define-property@1.0.0: - resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-module-lexer@1.5.4: - resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - - escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - eslint-config-prettier@9.1.0: - resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - - eslint-plugin-prettier@5.2.1: - resolution: {integrity: sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - '@types/eslint': '>=8.0.0' - eslint: '>=8.0.0' - eslint-config-prettier: '*' - prettier: '>=3.0.0' - peerDependenciesMeta: - '@types/eslint': - optional: true - eslint-config-prettier: - optional: true - - eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} - - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. - hasBin: true - - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - - exit@0.1.2: - resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} - engines: {node: '>= 0.8.0'} - - expect@29.7.0: - resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - express@4.21.1: - resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==} - engines: {node: '>= 0.10.0'} - - external-editor@3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} - engines: {node: '>=8.6.0'} - - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - - fast-safe-stringify@2.1.1: - resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - - fast-xml-parser@4.4.1: - resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} - hasBin: true - - fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - - fb-watchman@2.0.2: - resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} - - figures@3.2.0: - resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} - engines: {node: '>=8'} - - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - - filelist@1.0.4: - resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - finalhandler@1.3.1: - resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} - engines: {node: '>= 0.8'} - - find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} - - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} - - flat@5.0.2: - resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} - hasBin: true - - flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - - foreground-child@3.3.0: - resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} - engines: {node: '>=14'} - - fork-ts-checker-webpack-plugin@9.0.2: - resolution: {integrity: sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==} - engines: {node: '>=12.13.0', yarn: '>=1.0.0'} - peerDependencies: - typescript: '>3.6.0' - webpack: ^5.11.0 - - form-data@4.0.1: - resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} - engines: {node: '>= 6'} - - formidable@3.5.2: - resolution: {integrity: sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==} - - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - - fresh@0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} - engines: {node: '>= 0.6'} - - fs-extra@10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} - engines: {node: '>=12'} - - fs-monkey@1.0.6: - resolution: {integrity: sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==} - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - generate-function@2.3.1: - resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-intrinsic@1.2.4: - resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} - engines: {node: '>= 0.4'} - - get-package-type@0.1.0: - resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} - engines: {node: '>=8.0.0'} - - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - - glob-to-regexp@0.4.1: - resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - - glob@10.4.2: - resolution: {integrity: sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==} - engines: {node: '>=16 || 14 >=14.18'} - hasBin: true - - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - - globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} - - gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - has-own-prop@2.0.0: - resolution: {integrity: sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==} - engines: {node: '>=8'} - - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - - has-proto@1.0.3: - resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} - engines: {node: '>= 0.4'} - - has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - hexoid@2.0.0: - resolution: {integrity: sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==} - engines: {node: '>=8'} - - highlight.js@10.7.3: - resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} - - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - - http-errors@2.0.0: - resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} - engines: {node: '>= 0.8'} - - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} - - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - - import-local@3.2.0: - resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} - engines: {node: '>=8'} - hasBin: true - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - inquirer@8.2.6: - resolution: {integrity: sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==} - engines: {node: '>=12.0.0'} - - inquirer@9.2.15: - resolution: {integrity: sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==} - engines: {node: '>=18'} - - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-core-module@2.15.1: - resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} - engines: {node: '>= 0.4'} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-generator-fn@2.1.0: - resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} - engines: {node: '>=6'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-interactive@1.0.0: - resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} - engines: {node: '>=8'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - - is-property@1.0.2: - resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - - is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - - istanbul-lib-instrument@5.2.1: - resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} - engines: {node: '>=8'} - - istanbul-lib-instrument@6.0.3: - resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} - engines: {node: '>=10'} - - istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} - - istanbul-lib-source-maps@4.0.1: - resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} - engines: {node: '>=10'} - - istanbul-reports@3.1.7: - resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} - engines: {node: '>=8'} - - iterare@1.2.1: - resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} - engines: {node: '>=6'} - - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - - jake@10.9.2: - resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==} - engines: {node: '>=10'} - hasBin: true - - jest-changed-files@29.7.0: - resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-circus@29.7.0: - resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-cli@29.7.0: - resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - jest-config@29.7.0: - resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true - - jest-diff@29.7.0: - resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-docblock@29.7.0: - resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-each@29.7.0: - resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-environment-node@29.7.0: - resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-get-type@29.6.3: - resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-haste-map@29.7.0: - resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-leak-detector@29.7.0: - resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-matcher-utils@29.7.0: - resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-message-util@29.7.0: - resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-mock@29.7.0: - resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-pnp-resolver@1.2.3: - resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} - engines: {node: '>=6'} - peerDependencies: - jest-resolve: '*' - peerDependenciesMeta: - jest-resolve: - optional: true - - jest-regex-util@29.6.3: - resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-resolve-dependencies@29.7.0: - resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-resolve@29.7.0: - resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-runner@29.7.0: - resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-runtime@29.7.0: - resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-snapshot@29.7.0: - resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-util@29.7.0: - resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-validate@29.7.0: - resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-watcher@29.7.0: - resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-worker@27.5.1: - resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} - engines: {node: '>= 10.13.0'} - - jest-worker@29.7.0: - resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest@29.7.0: - resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - jiti@2.4.0: - resolution: {integrity: sha512-H5UpaUI+aHOqZXlYOaFP/8AzKsg+guWu+Pr3Y8i7+Y3zr1aXAvCvTAQ1RxSc6oVD8R8c7brgNtTVP91E7upH/g==} - hasBin: true - - joi@17.13.3: - resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true - - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - - jsesc@3.0.2: - resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} - engines: {node: '>=6'} - hasBin: true - - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - jsonc-parser@3.2.1: - resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} - - jsonc-parser@3.3.1: - resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} - - jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - - jsonwebtoken@9.0.2: - resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} - engines: {node: '>=12', npm: '>=6'} - - jwa@1.4.1: - resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} - - jws@3.2.2: - resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} - - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - - leven@3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} - engines: {node: '>=6'} - - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - - libphonenumber-js@1.11.13: - resolution: {integrity: sha512-LIJmXxgs7o1njVZPcX5fkbtcFgDnXXPvJQQBH5Ho/8+r6BFlJaEbJ+bAiaUGaChWUhFtvawwdmXIOz4wZBANCg==} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - loader-runner@4.3.0: - resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} - engines: {node: '>=6.11.5'} - - locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - locter@2.1.5: - resolution: {integrity: sha512-eI57PuVxigQ0GBscGIIFGPB467E5zKODHD3XGuknzLvf7HdnvRw3GdZVGj1J8XKsKOYovZQesX/oOdTwbdjwuQ==} - - lodash.includes@4.3.0: - resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} - - lodash.isboolean@3.0.3: - resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} - - lodash.isinteger@4.0.4: - resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} - - lodash.isnumber@3.0.3: - resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} - - lodash.isplainobject@4.0.6: - resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - - lodash.isstring@4.0.1: - resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} - - lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - lodash.once@4.1.1: - resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} - - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - - long@5.2.3: - resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} - - lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - lru-cache@7.18.3: - resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} - engines: {node: '>=12'} - - lru.min@1.1.1: - resolution: {integrity: sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==} - engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} - - magic-string@0.30.8: - resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} - engines: {node: '>=12'} - - make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} - - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - - makeerror@1.0.12: - resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} - - media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} - - memfs@3.5.3: - resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} - engines: {node: '>= 4.0.0'} - - merge-descriptors@1.0.3: - resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - methods@1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} - engines: {node: '>= 0.6'} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - mime@1.6.0: - resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} - engines: {node: '>=4'} - hasBin: true - - mime@2.6.0: - resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} - engines: {node: '>=4.0.0'} - hasBin: true - - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} - - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - - mkdirp@0.5.6: - resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} - hasBin: true - - mkdirp@2.1.6: - resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} - engines: {node: '>=10'} - hasBin: true - - ms@2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - multer@1.4.4-lts.1: - resolution: {integrity: sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==} - engines: {node: '>= 6.0.0'} - - mute-stream@0.0.8: - resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} - - mute-stream@1.0.0: - resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - mysql2@3.11.4: - resolution: {integrity: sha512-Z2o3tY4Z8EvSRDwknaC40MdZ3+m0sKbpnXrShQLdxPrAvcNli7jLrD2Zd2IzsRMw4eK9Yle500FDmlkIqp+krg==} - engines: {node: '>= 8.0'} - - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - - named-placeholders@1.1.3: - resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} - engines: {node: '>=12.0.0'} - - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} - - neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - - no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} - - node-abort-controller@3.1.1: - resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} - - node-addon-api@8.2.2: - resolution: {integrity: sha512-9emqXAKhVoNrQ792nLI/wpzPpJ/bj/YXxW0CvAau1+RdGBcCRF1Dmz7719zgVsQNrzHl9Tzn3ImZ4qWFarWL0A==} - engines: {node: ^18 || ^20 || >= 21} - - node-emoji@1.11.0: - resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} - - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - - node-gyp-build@4.8.2: - resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==} - hasBin: true - - node-int64@0.4.0: - resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - - node-releases@2.0.18: - resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.2: - resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} - engines: {node: '>= 0.4'} - - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - - ora@5.4.1: - resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} - engines: {node: '>=10'} - - os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} - - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - - parse5-htmlparser2-tree-adapter@6.0.1: - resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} - - parse5@5.1.1: - resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} - - parse5@6.0.1: - resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} - - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - - pascal-case@3.1.2: - resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - - path-to-regexp@0.1.10: - resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} - - path-to-regexp@3.3.0: - resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} - - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - - pg-cloudflare@1.1.1: - resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} - - pg-connection-string@2.7.0: - resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} - - pg-int8@1.0.1: - resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} - engines: {node: '>=4.0.0'} - - pg-pool@3.7.0: - resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==} - peerDependencies: - pg: '>=8.0' - - pg-protocol@1.7.0: - resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} - - pg-types@2.2.0: - resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} - engines: {node: '>=4'} - - pg@8.13.1: - resolution: {integrity: sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==} - engines: {node: '>= 8.0.0'} - peerDependencies: - pg-native: '>=3.0.1' - peerDependenciesMeta: - pg-native: - optional: true - - pgpass@1.0.5: - resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - picomatch@4.0.1: - resolution: {integrity: sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==} - engines: {node: '>=12'} - - pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} - engines: {node: '>= 6'} - - pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} - - pluralize@8.0.0: - resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} - engines: {node: '>=4'} - - postgres-array@2.0.0: - resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} - engines: {node: '>=4'} - - postgres-bytea@1.0.0: - resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} - engines: {node: '>=0.10.0'} - - postgres-date@1.0.7: - resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} - engines: {node: '>=0.10.0'} - - postgres-interval@1.2.0: - resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} - engines: {node: '>=0.10.0'} - - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prettier-linter-helpers@1.0.0: - resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} - engines: {node: '>=6.0.0'} - - prettier@3.3.3: - resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} - engines: {node: '>=14'} - hasBin: true - - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - pure-rand@6.1.0: - resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - - qs@6.13.0: - resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} - engines: {node: '>=0.6'} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - - rapiq@0.9.0: - resolution: {integrity: sha512-k4oT4RarFBrlLMJ49xUTeQpa/us0uU4I70D/UEnK3FWQ4GENzei01rEQAmvPKAIzACo4NMW+YcYJ7EVfSa7EFg==} - - raw-body@2.5.2: - resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} - engines: {node: '>= 0.8'} - - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - reflect-metadata@0.2.2: - resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} - - repeat-string@1.6.1: - resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} - engines: {node: '>=0.10'} - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - - resolve-cwd@3.0.0: - resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} - engines: {node: '>=8'} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - - resolve.exports@2.0.2: - resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} - engines: {node: '>=10'} - - resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} - hasBin: true - - restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} - - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - - run-async@2.4.1: - resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} - engines: {node: '>=0.12.0'} - - run-async@3.0.0: - resolution: {integrity: sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==} - engines: {node: '>=0.12.0'} - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} - - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - schema-utils@3.3.0: - resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} - engines: {node: '>= 10.13.0'} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} - engines: {node: '>=10'} - hasBin: true - - send@0.19.0: - resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} - engines: {node: '>= 0.8.0'} - - seq-queue@0.0.5: - resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} - - serialize-javascript@6.0.2: - resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - - serve-static@1.16.2: - resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} - engines: {node: '>= 0.8.0'} - - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - - sha.js@2.4.11: - resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} - hasBin: true - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - side-channel@1.0.6: - resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} - engines: {node: '>= 0.4'} - - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - smob@1.5.0: - resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} - - source-map-support@0.5.13: - resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} - - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - source-map@0.7.4: - resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} - engines: {node: '>= 8'} - - split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - - sqlstring@2.3.3: - resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} - engines: {node: '>= 0.6'} - - stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} - - statuses@2.0.1: - resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} - engines: {node: '>= 0.8'} - - std-env@3.7.0: - resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} - - streamsearch@1.1.0: - resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} - engines: {node: '>=10.0.0'} - - string-length@4.0.2: - resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} - engines: {node: '>=10'} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - - strip-bom@4.0.0: - resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} - engines: {node: '>=8'} - - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - strnum@1.0.5: - resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} - - superagent@9.0.2: - resolution: {integrity: sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==} - engines: {node: '>=14.18.0'} - - supertest@7.0.0: - resolution: {integrity: sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==} - engines: {node: '>=14.18.0'} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - swagger-ui-dist@5.18.2: - resolution: {integrity: sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw==} - - symbol-observable@4.0.0: - resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} - engines: {node: '>=0.10'} - - synckit@0.9.2: - resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==} - engines: {node: ^14.18.0 || >=16.0.0} - - tapable@2.2.1: - resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} - engines: {node: '>=6'} - - terser-webpack-plugin@5.3.10: - resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} - engines: {node: '>= 10.13.0'} - peerDependencies: - '@swc/core': '*' - esbuild: '*' - uglify-js: '*' - webpack: ^5.1.0 - peerDependenciesMeta: - '@swc/core': - optional: true - esbuild: - optional: true - uglify-js: - optional: true - - terser@5.36.0: - resolution: {integrity: sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==} - engines: {node: '>=10'} - hasBin: true - - test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} - - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - - tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} - - tmpl@1.0.5: - resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - - ts-api-utils@1.4.0: - resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - - ts-jest@29.2.5: - resolution: {integrity: sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==} - engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@babel/core': '>=7.0.0-beta.0 <8' - '@jest/transform': ^29.0.0 - '@jest/types': ^29.0.0 - babel-jest: ^29.0.0 - esbuild: '*' - jest: ^29.0.0 - typescript: '>=4.3 <6' - peerDependenciesMeta: - '@babel/core': - optional: true - '@jest/transform': - optional: true - '@jest/types': - optional: true - babel-jest: - optional: true - esbuild: - optional: true - - ts-loader@9.5.1: - resolution: {integrity: sha512-rNH3sK9kGZcH9dYzC7CewQm4NtxJTjSEVRJ2DyBZR7f8/wcta+iV44UPCXc5+nzDzivKtlzV6c9P4e+oFhDLYg==} - engines: {node: '>=12.0.0'} - peerDependencies: - typescript: '*' - webpack: ^5.0.0 - - ts-node@10.9.2: - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - - tsconfig-paths-webpack-plugin@4.1.0: - resolution: {integrity: sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==} - engines: {node: '>=10.13.0'} - - tsconfig-paths@4.2.0: - resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} - engines: {node: '>=6'} - - tslib@2.7.0: - resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - - type-is@1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} - - typedarray@0.0.6: - resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - - typeorm-extension@3.6.3: - resolution: {integrity: sha512-AE+8KqBphlBdVz5JS77o6LZzzi+b+YFFt8So4Qu/KRo/iynAwekrx98Oxuu3FAYNm6DUKDcubOBMZsJeiRvHkA==} - engines: {node: '>=14.0.0'} - hasBin: true - peerDependencies: - typeorm: ~0.3.0 - - typeorm@0.3.20: - resolution: {integrity: sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q==} - engines: {node: '>=16.13.0'} - hasBin: true - peerDependencies: - '@google-cloud/spanner': ^5.18.0 - '@sap/hana-client': ^2.12.25 - better-sqlite3: ^7.1.2 || ^8.0.0 || ^9.0.0 - hdb-pool: ^0.1.6 - ioredis: ^5.0.4 - mongodb: ^5.8.0 - mssql: ^9.1.1 || ^10.0.1 - mysql2: ^2.2.5 || ^3.0.1 - oracledb: ^6.3.0 - pg: ^8.5.1 - pg-native: ^3.0.0 - pg-query-stream: ^4.0.0 - redis: ^3.1.1 || ^4.0.0 - sql.js: ^1.4.0 - sqlite3: ^5.0.3 - ts-node: ^10.7.0 - typeorm-aurora-data-api-driver: ^2.0.0 - peerDependenciesMeta: - '@google-cloud/spanner': - optional: true - '@sap/hana-client': - optional: true - better-sqlite3: - optional: true - hdb-pool: - optional: true - ioredis: - optional: true - mongodb: - optional: true - mssql: - optional: true - mysql2: - optional: true - oracledb: - optional: true - pg: - optional: true - pg-native: - optional: true - pg-query-stream: - optional: true - redis: - optional: true - sql.js: - optional: true - sqlite3: - optional: true - ts-node: - optional: true - typeorm-aurora-data-api-driver: - optional: true - - typescript@5.6.3: - resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} - engines: {node: '>=14.17'} - hasBin: true - - uid@2.0.2: - resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} - engines: {node: '>=8'} - - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} - - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - - update-browserslist-db@1.1.1: - resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - utils-merge@1.0.1: - resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} - engines: {node: '>= 0.4.0'} - - uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - hasBin: true - - v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - - v8-to-istanbul@9.3.0: - resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} - engines: {node: '>=10.12.0'} - - validator@13.12.0: - resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} - engines: {node: '>= 0.10'} - - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - - walker@1.0.8: - resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} - - watchpack@2.4.2: - resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==} - engines: {node: '>=10.13.0'} - - wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} - - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - webpack-node-externals@3.0.0: - resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} - engines: {node: '>=6'} - - webpack-sources@3.2.3: - resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} - engines: {node: '>=10.13.0'} - - webpack@5.96.1: - resolution: {integrity: sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==} - engines: {node: '>=10.13.0'} - hasBin: true - peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - write-file-atomic@4.0.2: - resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} +dependencies: + '@aws-sdk/client-s3': + specifier: ^3.693.0 + version: 3.693.0 + '@nestjs/class-validator': + specifier: 0.13.1 + version: 0.13.1 + '@nestjs/common': + specifier: ^10.0.0 + version: 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/config': + specifier: ^3.3.0 + version: 3.3.0(@nestjs/common@10.0.0)(rxjs@7.8.1) + '@nestjs/core': + specifier: ^10.0.0 + version: 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/jwt': + specifier: ^10.2.0 + version: 10.2.0(@nestjs/common@10.0.0) + '@nestjs/mapped-types': + specifier: ^2.0.6 + version: 2.0.6(@nestjs/common@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) + '@nestjs/platform-express': + specifier: ^10.0.0 + version: 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0) + '@nestjs/swagger': + specifier: ^8.0.5 + version: 8.0.5(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) + '@nestjs/typeorm': + specifier: ^10.0.2 + version: 10.0.2(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)(typeorm@0.3.20) + '@smithy/types': + specifier: ^3.7.1 + version: 3.7.1 + argon2: + specifier: ^0.41.1 + version: 0.41.1 + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.1 + version: 0.14.1 + dotenv: + specifier: ^16.4.5 + version: 16.4.5 + joi: + specifier: ^17.13.3 + version: 17.13.3 + mysql2: + specifier: ^3.11.4 + version: 3.11.4 + pg: + specifier: ^8.13.1 + version: 8.13.1 + reflect-metadata: + specifier: ^0.2.0 + version: 0.2.0 + rxjs: + specifier: ^7.8.1 + version: 7.8.1 + typeorm: + specifier: ^0.3.20 + version: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1) + typeorm-extension: + specifier: ^3.6.3 + version: 3.6.3(typeorm@0.3.20) + +devDependencies: + '@nestjs/cli': + specifier: ^10.0.0 + version: 10.0.0 + '@nestjs/schematics': + specifier: ^10.0.0 + version: 10.0.0(chokidar@3.5.3)(typescript@5.1.3) + '@nestjs/testing': + specifier: ^10.0.0 + version: 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0) + '@types/express': + specifier: ^5.0.0 + version: 5.0.0 + '@types/jest': + specifier: ^29.5.2 + version: 29.5.2 + '@types/multer': + specifier: ^1.4.12 + version: 1.4.12 + '@types/node': + specifier: ^20.3.1 + version: 20.3.1 + '@types/supertest': + specifier: ^6.0.0 + version: 6.0.0 + '@typescript-eslint/eslint-plugin': + specifier: ^8.0.0 + version: 8.0.0(@typescript-eslint/parser@8.0.0)(eslint@8.0.0)(typescript@5.1.3) + '@typescript-eslint/parser': + specifier: ^8.0.0 + version: 8.0.0(eslint@8.0.0)(typescript@5.1.3) + eslint: + specifier: ^8.0.0 + version: 8.0.0 + eslint-config-prettier: + specifier: ^9.0.0 + version: 9.0.0(eslint@8.0.0) + eslint-plugin-prettier: + specifier: ^5.0.0 + version: 5.0.0(eslint-config-prettier@9.0.0)(eslint@8.0.0)(prettier@3.0.0) + jest: + specifier: ^29.5.0 + version: 29.5.0(@types/node@20.3.1)(ts-node@10.9.1) + prettier: + specifier: ^3.0.0 + version: 3.0.0 + source-map-support: + specifier: ^0.5.21 + version: 0.5.21 + supertest: + specifier: ^7.0.0 + version: 7.0.0 + ts-jest: + specifier: ^29.1.0 + version: 29.1.0(@babel/core@7.26.0)(jest@29.5.0)(typescript@5.1.3) + ts-loader: + specifier: ^9.4.3 + version: 9.4.3(typescript@5.1.3)(webpack@5.96.1) + ts-node: + specifier: ^10.9.1 + version: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) + tsconfig-paths: + specifier: ^4.2.0 + version: 4.2.0 + typescript: + specifier: ^5.1.3 + version: 5.1.3 - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - yaml@2.6.0: - resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==} - engines: {node: '>= 14'} - hasBin: true - - yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} - engines: {node: '>=10'} - - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - -snapshots: +packages: - '@ampproject/remapping@2.3.0': + /@ampproject/remapping@2.3.0: + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} dependencies: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 + dev: true - '@angular-devkit/core@17.3.11(chokidar@3.6.0)': + /@angular-devkit/core@16.1.0(chokidar@3.5.3): + resolution: {integrity: sha512-mrWpuDvttmhrCGcLc68RIXKtTzUhkBTsE5ZZFZNO1+FSC+vO/ZpyCpPd6C+6coM68NfXYjHlms5XF6KbxeGn/Q==} + engines: {node: ^16.14.0 || >=18.10.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^3.5.2 + peerDependenciesMeta: + chokidar: + optional: true dependencies: ajv: 8.12.0 ajv-formats: 2.1.1(ajv@8.12.0) - jsonc-parser: 3.2.1 - picomatch: 4.0.1 + chokidar: 3.5.3 + jsonc-parser: 3.2.0 rxjs: 7.8.1 source-map: 0.7.4 - optionalDependencies: - chokidar: 3.6.0 + dev: true - '@angular-devkit/schematics-cli@17.3.11(chokidar@3.6.0)': + /@angular-devkit/schematics-cli@16.1.0(chokidar@3.5.3): + resolution: {integrity: sha512-siBpRDmMMV7NB+NvaDHeJ4doHoSkFwIywwFj8GXnBCtobyxrBl1EyG1cKK+FHRydYtyYIk8FEoOpJA9oE9S2hg==} + engines: {node: ^16.14.0 || >=18.10.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + hasBin: true dependencies: - '@angular-devkit/core': 17.3.11(chokidar@3.6.0) - '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) + '@angular-devkit/core': 16.1.0(chokidar@3.5.3) + '@angular-devkit/schematics': 16.1.0(chokidar@3.5.3) ansi-colors: 4.1.3 - inquirer: 9.2.15 + inquirer: 8.2.4 symbol-observable: 4.0.0 yargs-parser: 21.1.1 transitivePeerDependencies: - chokidar + dev: true - '@angular-devkit/schematics@17.3.11(chokidar@3.6.0)': + /@angular-devkit/schematics@16.1.0(chokidar@3.5.3): + resolution: {integrity: sha512-LM35PH9DT3eQRSZgrkk2bx1ZQjjVh8BCByTlr37/c+FnF9mNbeBsa1YkxrlsN/CwO+045OwEwRHnkM9Zcx0U/A==} + engines: {node: ^16.14.0 || >=18.10.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} dependencies: - '@angular-devkit/core': 17.3.11(chokidar@3.6.0) - jsonc-parser: 3.2.1 - magic-string: 0.30.8 + '@angular-devkit/core': 16.1.0(chokidar@3.5.3) + jsonc-parser: 3.2.0 + magic-string: 0.30.0 ora: 5.4.1 rxjs: 7.8.1 transitivePeerDependencies: - chokidar + dev: true - '@aws-crypto/crc32@5.2.0': + /@aws-crypto/crc32@5.2.0: + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} dependencies: '@aws-crypto/util': 5.2.0 '@aws-sdk/types': 3.692.0 tslib: 2.8.1 + dev: false - '@aws-crypto/crc32c@5.2.0': + /@aws-crypto/crc32c@5.2.0: + resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} dependencies: '@aws-crypto/util': 5.2.0 '@aws-sdk/types': 3.692.0 tslib: 2.8.1 + dev: false - '@aws-crypto/sha1-browser@5.2.0': + /@aws-crypto/sha1-browser@5.2.0: + resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} dependencies: '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 @@ -3642,8 +221,10 @@ snapshots: '@aws-sdk/util-locate-window': 3.693.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 + dev: false - '@aws-crypto/sha256-browser@5.2.0': + /@aws-crypto/sha256-browser@5.2.0: + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} dependencies: '@aws-crypto/sha256-js': 5.2.0 '@aws-crypto/supports-web-crypto': 5.2.0 @@ -3652,24 +233,34 @@ snapshots: '@aws-sdk/util-locate-window': 3.693.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 + dev: false - '@aws-crypto/sha256-js@5.2.0': + /@aws-crypto/sha256-js@5.2.0: + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} dependencies: '@aws-crypto/util': 5.2.0 '@aws-sdk/types': 3.692.0 tslib: 2.8.1 + dev: false - '@aws-crypto/supports-web-crypto@5.2.0': + /@aws-crypto/supports-web-crypto@5.2.0: + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} dependencies: tslib: 2.8.1 + dev: false - '@aws-crypto/util@5.2.0': + /@aws-crypto/util@5.2.0: + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 + dev: false - '@aws-sdk/client-s3@3.693.0': + /@aws-sdk/client-s3@3.693.0: + resolution: {integrity: sha512-vgGI2e0Q6pzyhqfrSysi+sk/i+Nl+lMon67oqj/57RcCw9daL1/inpS+ADuwHpiPWkrg+U0bOXnmHjkLeTslJg==} + engines: {node: '>=16.0.0'} dependencies: '@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0 @@ -3677,7 +268,7 @@ snapshots: '@aws-sdk/client-sso-oidc': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/client-sts': 3.693.0 '@aws-sdk/core': 3.693.0 - '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) '@aws-sdk/middleware-bucket-endpoint': 3.693.0 '@aws-sdk/middleware-expect-continue': 3.693.0 '@aws-sdk/middleware-flexible-checksums': 3.693.0 @@ -3731,14 +322,19 @@ snapshots: tslib: 2.8.1 transitivePeerDependencies: - aws-crt + dev: false - '@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)': + /@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0): + resolution: {integrity: sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.693.0 dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 '@aws-sdk/client-sts': 3.693.0 '@aws-sdk/core': 3.693.0 - '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) '@aws-sdk/middleware-host-header': 3.693.0 '@aws-sdk/middleware-logger': 3.693.0 '@aws-sdk/middleware-recursion-detection': 3.693.0 @@ -3776,8 +372,11 @@ snapshots: tslib: 2.8.1 transitivePeerDependencies: - aws-crt + dev: false - '@aws-sdk/client-sso@3.693.0': + /@aws-sdk/client-sso@3.693.0: + resolution: {integrity: sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==} + engines: {node: '>=16.0.0'} dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 @@ -3819,14 +418,17 @@ snapshots: tslib: 2.8.1 transitivePeerDependencies: - aws-crt + dev: false - '@aws-sdk/client-sts@3.693.0': + /@aws-sdk/client-sts@3.693.0: + resolution: {integrity: sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==} + engines: {node: '>=16.0.0'} dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 '@aws-sdk/client-sso-oidc': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/core': 3.693.0 - '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) '@aws-sdk/middleware-host-header': 3.693.0 '@aws-sdk/middleware-logger': 3.693.0 '@aws-sdk/middleware-recursion-detection': 3.693.0 @@ -3864,8 +466,11 @@ snapshots: tslib: 2.8.1 transitivePeerDependencies: - aws-crt + dev: false - '@aws-sdk/core@3.693.0': + /@aws-sdk/core@3.693.0: + resolution: {integrity: sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/core': 2.5.3 @@ -3878,16 +483,22 @@ snapshots: '@smithy/util-middleware': 3.0.10 fast-xml-parser: 4.4.1 tslib: 2.8.1 + dev: false - '@aws-sdk/credential-provider-env@3.693.0': + /@aws-sdk/credential-provider-env@3.693.0: + resolution: {integrity: sha512-hMUZaRSF7+iBKZfBHNLihFs9zvpM1CB8MBOTnTp5NGCVkRYF3SB2LH+Kcippe0ats4qCyB1eEoyQX99rERp2iQ==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/core': 3.693.0 '@aws-sdk/types': 3.692.0 '@smithy/property-provider': 3.1.10 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/credential-provider-http@3.693.0': + /@aws-sdk/credential-provider-http@3.693.0: + resolution: {integrity: sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/core': 3.693.0 '@aws-sdk/types': 3.692.0 @@ -3899,15 +510,20 @@ snapshots: '@smithy/types': 3.7.1 '@smithy/util-stream': 3.3.1 tslib: 2.8.1 + dev: false - '@aws-sdk/credential-provider-ini@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0)': + /@aws-sdk/credential-provider-ini@3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0): + resolution: {integrity: sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.693.0 dependencies: '@aws-sdk/client-sts': 3.693.0 '@aws-sdk/core': 3.693.0 '@aws-sdk/credential-provider-env': 3.693.0 '@aws-sdk/credential-provider-http': 3.693.0 '@aws-sdk/credential-provider-process': 3.693.0 - '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)) + '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0) '@aws-sdk/credential-provider-web-identity': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/types': 3.692.0 '@smithy/credential-provider-imds': 3.2.7 @@ -3918,14 +534,17 @@ snapshots: transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt + dev: false - '@aws-sdk/credential-provider-node@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0)': + /@aws-sdk/credential-provider-node@3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0): + resolution: {integrity: sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/credential-provider-env': 3.693.0 '@aws-sdk/credential-provider-http': 3.693.0 - '@aws-sdk/credential-provider-ini': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-ini': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) '@aws-sdk/credential-provider-process': 3.693.0 - '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)) + '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0) '@aws-sdk/credential-provider-web-identity': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/types': 3.692.0 '@smithy/credential-provider-imds': 3.2.7 @@ -3937,8 +556,11 @@ snapshots: - '@aws-sdk/client-sso-oidc' - '@aws-sdk/client-sts' - aws-crt + dev: false - '@aws-sdk/credential-provider-process@3.693.0': + /@aws-sdk/credential-provider-process@3.693.0: + resolution: {integrity: sha512-cvxQkrTWHHjeHrPlj7EWXPnFSq8x7vMx+Zn1oTsMpCY445N9KuzjfJTkmNGwU2GT6rSZI9/0MM02aQvl5bBBTQ==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/core': 3.693.0 '@aws-sdk/types': 3.692.0 @@ -3946,12 +568,15 @@ snapshots: '@smithy/shared-ini-file-loader': 3.1.11 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/credential-provider-sso@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))': + /@aws-sdk/credential-provider-sso@3.693.0(@aws-sdk/client-sso-oidc@3.693.0): + resolution: {integrity: sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/client-sso': 3.693.0 '@aws-sdk/core': 3.693.0 - '@aws-sdk/token-providers': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)) + '@aws-sdk/token-providers': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0) '@aws-sdk/types': 3.692.0 '@smithy/property-provider': 3.1.10 '@smithy/shared-ini-file-loader': 3.1.11 @@ -3960,8 +585,13 @@ snapshots: transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt + dev: false - '@aws-sdk/credential-provider-web-identity@3.693.0(@aws-sdk/client-sts@3.693.0)': + /@aws-sdk/credential-provider-web-identity@3.693.0(@aws-sdk/client-sts@3.693.0): + resolution: {integrity: sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.693.0 dependencies: '@aws-sdk/client-sts': 3.693.0 '@aws-sdk/core': 3.693.0 @@ -3969,8 +599,11 @@ snapshots: '@smithy/property-provider': 3.1.10 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-bucket-endpoint@3.693.0': + /@aws-sdk/middleware-bucket-endpoint@3.693.0: + resolution: {integrity: sha512-cPIa+lxMYiFRHtxKfNIVSFGO6LSgZCk42pu3d7KGwD6hu6vXRD5B2/DD3rPcEH1zgl2j0Kx1oGAV7SRXKHSFag==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@aws-sdk/util-arn-parser': 3.693.0 @@ -3979,15 +612,21 @@ snapshots: '@smithy/types': 3.7.1 '@smithy/util-config-provider': 3.0.0 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-expect-continue@3.693.0': + /@aws-sdk/middleware-expect-continue@3.693.0: + resolution: {integrity: sha512-MuK/gsJWpHz6Tv0CqTCS+QNOxLa2RfPh1biVCu/uO3l7kA0TjQ/C+tfgKvLXeH103tuDrOVINK+bt2ENmI3SWg==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/protocol-http': 4.1.7 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-flexible-checksums@3.693.0': + /@aws-sdk/middleware-flexible-checksums@3.693.0: + resolution: {integrity: sha512-xkS6zjuE11ob93H9t65kHzphXcUMnN2SmIm2wycUPg+hi8Q6DJA6U2p//6oXkrr9oHy1QvwtllRd7SAd63sFKQ==} + engines: {node: '>=16.0.0'} dependencies: '@aws-crypto/crc32': 5.2.0 '@aws-crypto/crc32c': 5.2.0 @@ -4002,34 +641,49 @@ snapshots: '@smithy/util-stream': 3.3.1 '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-host-header@3.693.0': + /@aws-sdk/middleware-host-header@3.693.0: + resolution: {integrity: sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/protocol-http': 4.1.7 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-location-constraint@3.693.0': + /@aws-sdk/middleware-location-constraint@3.693.0: + resolution: {integrity: sha512-eDAExTZ9uNIP7vs2JCVCOuWJauGueisBSn+Ovt7UvvuEUp6KOIJqn8oFxWmyUQu2GvbG4OcaTLgbqD95YHTB0Q==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-logger@3.693.0': + /@aws-sdk/middleware-logger@3.693.0: + resolution: {integrity: sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-recursion-detection@3.693.0': + /@aws-sdk/middleware-recursion-detection@3.693.0: + resolution: {integrity: sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/protocol-http': 4.1.7 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-sdk-s3@3.693.0': + /@aws-sdk/middleware-sdk-s3@3.693.0: + resolution: {integrity: sha512-5A++RBjJ3guyq5pbYs+Oq5hMlA8CK2OWaHx09cxVfhHWl/RoaY8DXrft4gnhoUEBrrubyMw7r9j7RIMLvS58kg==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/core': 3.693.0 '@aws-sdk/types': 3.692.0 @@ -4045,14 +699,20 @@ snapshots: '@smithy/util-stream': 3.3.1 '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-ssec@3.693.0': + /@aws-sdk/middleware-ssec@3.693.0: + resolution: {integrity: sha512-Ro5vzI7SRgEeuoMk3fKqFjGv6mG4c7VsSCDwnkiasmafQFBTPvUIpgmu2FXMHqW/OthvoiOzpSrlJ9Bwlx2f8A==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-user-agent@3.693.0': + /@aws-sdk/middleware-user-agent@3.693.0: + resolution: {integrity: sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/core': 3.693.0 '@aws-sdk/types': 3.692.0 @@ -4061,8 +721,11 @@ snapshots: '@smithy/protocol-http': 4.1.7 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/region-config-resolver@3.693.0': + /@aws-sdk/region-config-resolver@3.693.0: + resolution: {integrity: sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/node-config-provider': 3.1.11 @@ -4070,8 +733,11 @@ snapshots: '@smithy/util-config-provider': 3.0.0 '@smithy/util-middleware': 3.0.10 tslib: 2.8.1 + dev: false - '@aws-sdk/signature-v4-multi-region@3.693.0': + /@aws-sdk/signature-v4-multi-region@3.693.0: + resolution: {integrity: sha512-s7zbbsoVIriTR4ZGaateKuTqz6ddpazAyHvjk7I9kd+NvGNPiuAI18UdbuiiRI6K5HuYKf1ah6mKWFGPG15/kQ==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/middleware-sdk-s3': 3.693.0 '@aws-sdk/types': 3.692.0 @@ -4079,8 +745,13 @@ snapshots: '@smithy/signature-v4': 4.2.3 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/token-providers@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))': + /@aws-sdk/token-providers@3.693.0(@aws-sdk/client-sso-oidc@3.693.0): + resolution: {integrity: sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sso-oidc': ^3.693.0 dependencies: '@aws-sdk/client-sso-oidc': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/types': 3.692.0 @@ -4088,56 +759,90 @@ snapshots: '@smithy/shared-ini-file-loader': 3.1.11 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/types@3.692.0': + /@aws-sdk/types@3.692.0: + resolution: {integrity: sha512-RpNvzD7zMEhiKgmlxGzyXaEcg2khvM7wd5sSHVapOcrde1awQSOMGI4zKBQ+wy5TnDfrm170ROz/ERLYtrjPZA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/util-arn-parser@3.693.0': + /@aws-sdk/util-arn-parser@3.693.0: + resolution: {integrity: sha512-WC8x6ca+NRrtpAH64rWu+ryDZI3HuLwlEr8EU6/dbC/pt+r/zC0PBoC15VEygUaBA+isppCikQpGyEDu0Yj7gQ==} + engines: {node: '>=16.0.0'} dependencies: tslib: 2.8.1 + dev: false - '@aws-sdk/util-endpoints@3.693.0': + /@aws-sdk/util-endpoints@3.693.0: + resolution: {integrity: sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/types': 3.7.1 '@smithy/util-endpoints': 2.1.6 tslib: 2.8.1 + dev: false - '@aws-sdk/util-locate-window@3.693.0': + /@aws-sdk/util-locate-window@3.693.0: + resolution: {integrity: sha512-ttrag6haJLWABhLqtg1Uf+4LgHWIMOVSYL+VYZmAp2v4PUGOwWmWQH0Zk8RM7YuQcLfH/EoR72/Yxz6A4FKcuw==} + engines: {node: '>=16.0.0'} dependencies: tslib: 2.8.1 + dev: false - '@aws-sdk/util-user-agent-browser@3.693.0': + /@aws-sdk/util-user-agent-browser@3.693.0: + resolution: {integrity: sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/types': 3.7.1 bowser: 2.11.0 tslib: 2.8.1 + dev: false - '@aws-sdk/util-user-agent-node@3.693.0': + /@aws-sdk/util-user-agent-node@3.693.0: + resolution: {integrity: sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==} + engines: {node: '>=16.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true dependencies: '@aws-sdk/middleware-user-agent': 3.693.0 '@aws-sdk/types': 3.692.0 '@smithy/node-config-provider': 3.1.11 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/xml-builder@3.693.0': + /@aws-sdk/xml-builder@3.693.0: + resolution: {integrity: sha512-C/rPwJcqnV8VDr2/VtcQnymSpcfEEgH1Jm6V0VmfXNZFv4Qzf1eCS8nsec0gipYgZB+cBBjfXw5dAk6pJ8ubpw==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@babel/code-frame@7.26.2': + /@babel/code-frame@7.26.2: + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} dependencies: '@babel/helper-validator-identifier': 7.25.9 js-tokens: 4.0.0 picocolors: 1.1.1 + dev: true - '@babel/compat-data@7.26.2': {} + /@babel/compat-data@7.26.2: + resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} + engines: {node: '>=6.9.0'} + dev: true - '@babel/core@7.26.0': + /@babel/core@7.26.0: + resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} + engines: {node: '>=6.9.0'} dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.26.2 @@ -4156,31 +861,45 @@ snapshots: semver: 6.3.1 transitivePeerDependencies: - supports-color + dev: true - '@babel/generator@7.26.2': + /@babel/generator@7.26.2: + resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} + engines: {node: '>=6.9.0'} dependencies: '@babel/parser': 7.26.2 '@babel/types': 7.26.0 '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.0.2 + dev: true - '@babel/helper-compilation-targets@7.25.9': + /@babel/helper-compilation-targets@7.25.9: + resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} + engines: {node: '>=6.9.0'} dependencies: '@babel/compat-data': 7.26.2 '@babel/helper-validator-option': 7.25.9 browserslist: 4.24.2 lru-cache: 5.1.1 semver: 6.3.1 + dev: true - '@babel/helper-module-imports@7.25.9': + /@babel/helper-module-imports@7.25.9: + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} dependencies: '@babel/traverse': 7.25.9 '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color + dev: true - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': + /@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0): + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.26.0 '@babel/helper-module-imports': 7.25.9 @@ -4188,116 +907,215 @@ snapshots: '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color + dev: true - '@babel/helper-plugin-utils@7.25.9': {} + /@babel/helper-plugin-utils@7.25.9: + resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} + engines: {node: '>=6.9.0'} + dev: true - '@babel/helper-string-parser@7.25.9': {} + /@babel/helper-string-parser@7.25.9: + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + dev: true - '@babel/helper-validator-identifier@7.25.9': {} + /@babel/helper-validator-identifier@7.25.9: + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + dev: true - '@babel/helper-validator-option@7.25.9': {} + /@babel/helper-validator-option@7.25.9: + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + dev: true - '@babel/helpers@7.26.0': + /@babel/helpers@7.26.0: + resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} + engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.25.9 '@babel/types': 7.26.0 + dev: true - '@babel/parser@7.26.2': + /@babel/parser@7.26.2: + resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} + engines: {node: '>=6.0.0'} + hasBin: true dependencies: '@babel/types': 7.26.0 + dev: true - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.0)': + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.0): + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.0)': + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.0): + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.0)': + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.0): + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.0)': + /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.0): + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.0)': + /@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.0): + resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.0)': + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.0): + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.0)': + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.0): + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': + /@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0): + resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.0)': + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.0): + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.0)': + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.0): + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.0)': + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.0): + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.0)': + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.0): + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.0)': + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.0): + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.0)': + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.0): + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.0)': + /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.0): + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.0)': + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.0): + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)': + /@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0): + resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/template@7.25.9': + /@babel/template@7.25.9: + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} + engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.26.2 '@babel/parser': 7.26.2 '@babel/types': 7.26.0 + dev: true - '@babel/traverse@7.25.9': + /@babel/traverse@7.25.9: + resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} + engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.26.2 '@babel/generator': 7.26.2 @@ -4308,29 +1126,51 @@ snapshots: globals: 11.12.0 transitivePeerDependencies: - supports-color + dev: true - '@babel/types@7.26.0': + /@babel/types@7.26.0: + resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} + engines: {node: '>=6.9.0'} dependencies: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 + dev: true - '@bcoe/v8-coverage@0.2.3': {} + /@bcoe/v8-coverage@0.2.3: + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + dev: true - '@colors/colors@1.5.0': + /@colors/colors@1.5.0: + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + requiresBuild: true + dev: true optional: true - '@cspotcode/source-map-support@0.8.1': + /@cspotcode/source-map-support@0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)': + /@eslint-community/eslint-utils@4.4.1(eslint@8.0.0): + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.57.1 + eslint: 8.0.0 eslint-visitor-keys: 3.4.3 + dev: true - '@eslint-community/regexpp@4.12.1': {} + /@eslint-community/regexpp@4.12.1: + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true - '@eslint/eslintrc@2.1.4': + /@eslint/eslintrc@1.4.1: + resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 debug: 4.3.7 @@ -4343,72 +1183,102 @@ snapshots: strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color + dev: true - '@eslint/js@8.57.1': {} - - '@faker-js/faker@8.4.1': {} + /@faker-js/faker@8.4.1: + resolution: {integrity: sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'} + dev: false - '@hapi/hoek@9.3.0': {} + /@hapi/hoek@9.3.0: + resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + dev: false - '@hapi/topo@5.1.0': + /@hapi/topo@5.1.0: + resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} dependencies: '@hapi/hoek': 9.3.0 + dev: false - '@humanwhocodes/config-array@0.13.0': + /@humanwhocodes/config-array@0.6.0: + resolution: {integrity: sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead dependencies: - '@humanwhocodes/object-schema': 2.0.3 + '@humanwhocodes/object-schema': 1.2.1 debug: 4.3.7 minimatch: 3.1.2 transitivePeerDependencies: - supports-color + dev: true - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/object-schema@2.0.3': {} + /@humanwhocodes/object-schema@1.2.1: + resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + deprecated: Use @eslint/object-schema instead + dev: true - '@isaacs/cliui@8.0.2': + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} dependencies: string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 + string-width-cjs: /string-width@4.2.3 strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 + strip-ansi-cjs: /strip-ansi@6.0.1 wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: false - '@istanbuljs/load-nyc-config@1.1.0': + /@istanbuljs/load-nyc-config@1.1.0: + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} dependencies: camelcase: 5.3.1 find-up: 4.1.0 get-package-type: 0.1.0 js-yaml: 3.14.1 resolve-from: 5.0.0 + dev: true - '@istanbuljs/schema@0.1.3': {} + /@istanbuljs/schema@0.1.3: + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + dev: true - '@jest/console@29.7.0': + /@jest/console@29.7.0: + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.17.6 + '@types/node': 20.3.1 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 slash: 3.0.0 + dev: true - '@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3))': + /@jest/core@29.7.0(ts-node@10.9.1): + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.6 + '@types/node': 20.3.1 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -4428,35 +1298,50 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + dev: true - '@jest/environment@29.7.0': + /@jest/environment@29.7.0: + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.6 + '@types/node': 20.3.1 jest-mock: 29.7.0 + dev: true - '@jest/expect-utils@29.7.0': + /@jest/expect-utils@29.7.0: + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: jest-get-type: 29.6.3 + dev: true - '@jest/expect@29.7.0': + /@jest/expect@29.7.0: + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: expect: 29.7.0 jest-snapshot: 29.7.0 transitivePeerDependencies: - supports-color + dev: true - '@jest/fake-timers@29.7.0': + /@jest/fake-timers@29.7.0: + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.17.6 + '@types/node': 20.3.1 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 + dev: true - '@jest/globals@29.7.0': + /@jest/globals@29.7.0: + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/environment': 29.7.0 '@jest/expect': 29.7.0 @@ -4464,8 +1349,16 @@ snapshots: jest-mock: 29.7.0 transitivePeerDependencies: - supports-color + dev: true - '@jest/reporters@29.7.0': + /@jest/reporters@29.7.0: + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true dependencies: '@bcoe/v8-coverage': 0.2.3 '@jest/console': 29.7.0 @@ -4473,7 +1366,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 20.17.6 + '@types/node': 20.3.1 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -4493,32 +1386,47 @@ snapshots: v8-to-istanbul: 9.3.0 transitivePeerDependencies: - supports-color + dev: true - '@jest/schemas@29.6.3': + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@sinclair/typebox': 0.27.8 + dev: true - '@jest/source-map@29.6.3': + /@jest/source-map@29.6.3: + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jridgewell/trace-mapping': 0.3.25 callsites: 3.1.0 graceful-fs: 4.2.11 + dev: true - '@jest/test-result@29.7.0': + /@jest/test-result@29.7.0: + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/console': 29.7.0 '@jest/types': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 collect-v8-coverage: 1.0.2 + dev: true - '@jest/test-sequencer@29.7.0': + /@jest/test-sequencer@29.7.0: + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/test-result': 29.7.0 graceful-fs: 4.2.11 jest-haste-map: 29.7.0 slash: 3.0.0 + dev: true - '@jest/transform@29.7.0': + /@jest/transform@29.7.0: + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/core': 7.26.0 '@jest/types': 29.6.3 @@ -4537,200 +1445,331 @@ snapshots: write-file-atomic: 4.0.2 transitivePeerDependencies: - supports-color + dev: true - '@jest/types@29.6.3': + /@jest/types@29.6.3: + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.17.6 + '@types/node': 20.3.1 '@types/yargs': 17.0.33 chalk: 4.1.2 + dev: true - '@jridgewell/gen-mapping@0.3.5': + /@jridgewell/gen-mapping@0.3.5: + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} dependencies: '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.25 + dev: true - '@jridgewell/resolve-uri@3.1.2': {} + /@jridgewell/resolve-uri@3.1.2: + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} - '@jridgewell/set-array@1.2.1': {} + /@jridgewell/set-array@1.2.1: + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + dev: true - '@jridgewell/source-map@0.3.6': + /@jridgewell/source-map@0.3.6: + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} dependencies: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 + dev: true - '@jridgewell/sourcemap-codec@1.5.0': {} + /@jridgewell/sourcemap-codec@1.5.0: + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - '@jridgewell/trace-mapping@0.3.25': + /@jridgewell/trace-mapping@0.3.25: + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + dev: true - '@jridgewell/trace-mapping@0.3.9': + /@jridgewell/trace-mapping@0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@ljharb/through@2.3.13': - dependencies: - call-bind: 1.0.7 - - '@lukeed/csprng@1.1.0': {} + /@lukeed/csprng@1.1.0: + resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} + engines: {node: '>=8'} - '@microsoft/tsdoc@0.15.0': {} + /@microsoft/tsdoc@0.15.0: + resolution: {integrity: sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==} + dev: false - '@nestjs/class-validator@0.13.1': + /@nestjs/class-validator@0.13.1: + resolution: {integrity: sha512-Wwpg9KuGnkWOFleBPEFdHQKwqZsF/PFWQaeSaXwatkofgqD7N6AV5J30xIVgScDP+IcE+MdPGZJ38vHQ24KxPQ==} dependencies: '@types/validator': 13.12.2 - libphonenumber-js: 1.11.13 + libphonenumber-js: 1.11.14 validator: 13.12.0 + dev: false - '@nestjs/cli@10.4.7': + /@nestjs/cli@10.0.0: + resolution: {integrity: sha512-14pju3ejAAUpFe1iK99v/b7Bw96phBMV58GXTSm3TcdgaI4O7UTLXTbMiUNyU+LGr/1CPIfThcWqFyKhDIC9VQ==} + engines: {node: '>= 16'} + hasBin: true + peerDependencies: + '@swc/cli': ^0.1.62 + '@swc/core': ^1.3.62 + peerDependenciesMeta: + '@swc/cli': + optional: true + '@swc/core': + optional: true dependencies: - '@angular-devkit/core': 17.3.11(chokidar@3.6.0) - '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) - '@angular-devkit/schematics-cli': 17.3.11(chokidar@3.6.0) - '@nestjs/schematics': 10.2.3(chokidar@3.6.0)(typescript@5.6.3) + '@angular-devkit/core': 16.1.0(chokidar@3.5.3) + '@angular-devkit/schematics': 16.1.0(chokidar@3.5.3) + '@angular-devkit/schematics-cli': 16.1.0(chokidar@3.5.3) + '@nestjs/schematics': 10.0.0(chokidar@3.5.3)(typescript@5.1.3) chalk: 4.1.2 - chokidar: 3.6.0 - cli-table3: 0.6.5 + chokidar: 3.5.3 + cli-table3: 0.6.3 commander: 4.1.1 - fork-ts-checker-webpack-plugin: 9.0.2(typescript@5.6.3)(webpack@5.96.1) - glob: 10.4.2 - inquirer: 8.2.6 + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.1.3)(webpack@5.87.0) + inquirer: 8.2.5 node-emoji: 1.11.0 ora: 5.4.1 + os-name: 4.0.1 + rimraf: 4.4.1 + shelljs: 0.8.5 + source-map-support: 0.5.21 tree-kill: 1.2.2 tsconfig-paths: 4.2.0 - tsconfig-paths-webpack-plugin: 4.1.0 - typescript: 5.6.3 - webpack: 5.96.1 + tsconfig-paths-webpack-plugin: 4.0.1 + typescript: 5.1.3 + webpack: 5.87.0 webpack-node-externals: 3.0.0 transitivePeerDependencies: - esbuild - uglify-js - webpack-cli + dev: true - '@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)': + /@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1): + resolution: {integrity: sha512-Fa2GDQJrO5TTTcpISWfm0pdPS62V+8YbxeG5CA01zMUI+dCO3v3oFf+BSjqCGUUo7GDNzDsjAejwGXuqA54RPw==} + peerDependencies: + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 + rxjs: ^7.1.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true dependencies: + class-transformer: 0.5.1 + class-validator: 0.14.1 iterare: 1.2.1 - reflect-metadata: 0.2.2 + reflect-metadata: 0.2.0 rxjs: 7.8.1 - tslib: 2.7.0 + tslib: 2.5.3 uid: 2.0.2 - optionalDependencies: - class-transformer: 0.5.1 - class-validator: 0.14.1 - '@nestjs/config@3.3.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(rxjs@7.8.1)': + /@nestjs/config@3.3.0(@nestjs/common@10.0.0)(rxjs@7.8.1): + resolution: {integrity: sha512-pdGTp8m9d0ZCrjTpjkUbZx6gyf2IKf+7zlkrPNMsJzYZ4bFRRTpXrnj+556/5uiI6AfL5mMrJc2u7dB6bvM+VA==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + rxjs: ^7.1.0 dependencies: - '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) dotenv: 16.4.5 dotenv-expand: 10.0.0 lodash: 4.17.21 rxjs: 7.8.1 + dev: false - '@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1)': + /@nestjs/core@10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1): + resolution: {integrity: sha512-HFTdj4vsF+2qOaq97ZPRDle6Q/KyL5lmMah0/ZR0ie+e1/tnlvmlqw589xFACTemLJFFOjZMy763v+icO9u72w==} + requiresBuild: true + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/microservices': ^10.0.0 + '@nestjs/platform-express': ^10.0.0 + '@nestjs/websockets': ^10.0.0 + reflect-metadata: ^0.1.12 + rxjs: ^7.1.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true + '@nestjs/websockets': + optional: true dependencies: - '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0) '@nuxtjs/opencollective': 0.3.2 fast-safe-stringify: 2.1.1 iterare: 1.2.1 - path-to-regexp: 3.3.0 - reflect-metadata: 0.2.2 + path-to-regexp: 3.2.0 + reflect-metadata: 0.2.0 rxjs: 7.8.1 - tslib: 2.7.0 + tslib: 2.5.3 uid: 2.0.2 - optionalDependencies: - '@nestjs/platform-express': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7) transitivePeerDependencies: - encoding - '@nestjs/jwt@10.2.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))': + /@nestjs/jwt@10.2.0(@nestjs/common@10.0.0): + resolution: {integrity: sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 dependencies: - '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) '@types/jsonwebtoken': 9.0.5 jsonwebtoken: 9.0.2 + dev: false - '@nestjs/mapped-types@2.0.6(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)': + /@nestjs/mapped-types@2.0.6(@nestjs/common@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0): + resolution: {integrity: sha512-84ze+CPfp1OWdpRi1/lOu59hOhTz38eVzJvRKrg9ykRFwDz+XleKfMsG0gUqNZYFa6v53XYzeD+xItt8uDW7NQ==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + class-transformer: ^0.4.0 || ^0.5.0 + class-validator: ^0.13.0 || ^0.14.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true dependencies: - '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) - reflect-metadata: 0.2.2 - optionalDependencies: + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) class-transformer: 0.5.1 class-validator: 0.14.1 + reflect-metadata: 0.2.0 + dev: false - '@nestjs/platform-express@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)': + /@nestjs/platform-express@10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0): + resolution: {integrity: sha512-jOQBPVpk7B4JFXZZwxHSsY6odIqZlea9CbqKzu/hfDyqRv+AwuJk5gprvvL6RpWAHNyRMH1r5/14bqcXD3+WGw==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/core': ^10.0.0 dependencies: - '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) - body-parser: 1.20.3 + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + body-parser: 1.20.2 cors: 2.8.5 - express: 4.21.1 + express: 4.18.2 multer: 1.4.4-lts.1 - tslib: 2.7.0 + tslib: 2.5.3 transitivePeerDependencies: - supports-color - '@nestjs/schematics@10.2.3(chokidar@3.6.0)(typescript@5.6.3)': + /@nestjs/schematics@10.0.0(chokidar@3.5.3)(typescript@5.1.3): + resolution: {integrity: sha512-gfUy/N1m1paN33BXq4d7HoCM+zM4rFxYjqAb8jkrBfBHiwyEhHHozfX/aRy/kOnAcy/VP8v4Zs4HKKrbRRlHnw==} + peerDependencies: + typescript: '>=4.8.2' dependencies: - '@angular-devkit/core': 17.3.11(chokidar@3.6.0) - '@angular-devkit/schematics': 17.3.11(chokidar@3.6.0) - comment-json: 4.2.5 - jsonc-parser: 3.3.1 + '@angular-devkit/core': 16.1.0(chokidar@3.5.3) + '@angular-devkit/schematics': 16.1.0(chokidar@3.5.3) + comment-json: 4.2.3 + jsonc-parser: 3.2.0 pluralize: 8.0.0 - typescript: 5.6.3 + typescript: 5.1.3 transitivePeerDependencies: - chokidar + dev: true - '@nestjs/swagger@8.0.5(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)': + /@nestjs/swagger@8.0.5(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0): + resolution: {integrity: sha512-ZmBdsbQNs3wIN5kCuvAVbz3/ULh3gi814oHTP49uTqAGi1aT0YSatUyncwQOHBOlRT+rwF+TNjoAsZ+twIk/Jw==} + peerDependencies: + '@fastify/static': ^6.0.0 || ^7.0.0 + '@nestjs/common': ^9.0.0 || ^10.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + '@fastify/static': + optional: true + class-transformer: + optional: true + class-validator: + optional: true dependencies: '@microsoft/tsdoc': 0.15.0 - '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/mapped-types': 2.0.6(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/mapped-types': 2.0.6(@nestjs/common@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) + class-transformer: 0.5.1 + class-validator: 0.14.1 js-yaml: 4.1.0 lodash: 4.17.21 path-to-regexp: 3.3.0 - reflect-metadata: 0.2.2 + reflect-metadata: 0.2.0 swagger-ui-dist: 5.18.2 - optionalDependencies: - class-transformer: 0.5.1 - class-validator: 0.14.1 + dev: false - '@nestjs/testing@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@nestjs/platform-express@10.4.7)': + /@nestjs/testing@10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0): + resolution: {integrity: sha512-U5q3+svkddpdSk51ZFCEnFpQuWxAwE4ahsX77FrqqCAYidr7HUtL/BHYOVzI5H9vUH6BvJxMbfo3tiUXQl/2aA==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/core': ^10.0.0 + '@nestjs/microservices': ^10.0.0 + '@nestjs/platform-express': ^10.0.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true dependencies: - '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) - tslib: 2.7.0 - optionalDependencies: - '@nestjs/platform-express': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7) + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0) + tslib: 2.5.3 + dev: true - '@nestjs/typeorm@10.0.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1)(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)))': + /@nestjs/typeorm@10.0.2(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)(typeorm@0.3.20): + resolution: {integrity: sha512-H738bJyydK4SQkRCTeh1aFBxoO1E9xdL/HaLGThwrqN95os5mEyAtK7BLADOS+vldP4jDZ2VQPLj4epWwRqCeQ==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + reflect-metadata: ^0.1.13 || ^0.2.0 + rxjs: ^7.2.0 + typeorm: ^0.3.0 dependencies: - '@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1) - reflect-metadata: 0.2.2 + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + reflect-metadata: 0.2.0 rxjs: 7.8.1 - typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1) uuid: 9.0.1 + dev: false - '@nodelib/fs.scandir@2.1.5': + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 - '@nodelib/fs.stat@2.0.5': {} + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} - '@nodelib/fs.walk@1.2.8': + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} dependencies: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 - '@nuxtjs/opencollective@0.3.2': + /@nuxtjs/opencollective@0.3.2: + resolution: {integrity: sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true dependencies: chalk: 4.1.2 consola: 2.15.3 @@ -4738,56 +1777,93 @@ snapshots: transitivePeerDependencies: - encoding - '@phc/format@1.0.0': {} + /@phc/format@1.0.0: + resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==} + engines: {node: '>=10'} + dev: false - '@pkgjs/parseargs@0.11.0': + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + dev: false optional: true - '@pkgr/core@0.1.1': {} + /@pkgr/core@0.1.1: + resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + dev: true - '@scarf/scarf@1.4.0': {} + /@scarf/scarf@1.4.0: + resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} + requiresBuild: true + dev: false - '@sideway/address@4.1.5': + /@sideway/address@4.1.5: + resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} dependencies: '@hapi/hoek': 9.3.0 + dev: false - '@sideway/formula@3.0.1': {} + /@sideway/formula@3.0.1: + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + dev: false - '@sideway/pinpoint@2.0.0': {} + /@sideway/pinpoint@2.0.0: + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + dev: false - '@sinclair/typebox@0.27.8': {} + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true - '@sinonjs/commons@3.0.1': + /@sinonjs/commons@3.0.1: + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} dependencies: type-detect: 4.0.8 + dev: true - '@sinonjs/fake-timers@10.3.0': + /@sinonjs/fake-timers@10.3.0: + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} dependencies: '@sinonjs/commons': 3.0.1 + dev: true - '@smithy/abort-controller@3.1.8': + /@smithy/abort-controller@3.1.8: + resolution: {integrity: sha512-+3DOBcUn5/rVjlxGvUPKc416SExarAQ+Qe0bqk30YSUjbepwpS7QN0cyKUSifvLJhdMZ0WPzPP5ymut0oonrpQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/chunked-blob-reader-native@3.0.1': + /@smithy/chunked-blob-reader-native@3.0.1: + resolution: {integrity: sha512-VEYtPvh5rs/xlyqpm5NRnfYLZn+q0SRPELbvBV+C/G7IQ+ouTuo+NKKa3ShG5OaFR8NYVMXls9hPYLTvIKKDrQ==} dependencies: '@smithy/util-base64': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/chunked-blob-reader@4.0.0': + /@smithy/chunked-blob-reader@4.0.0: + resolution: {integrity: sha512-jSqRnZvkT4egkq/7b6/QRCNXmmYVcHwnJldqJ3IhVpQE2atObVJ137xmGeuGFhjFUr8gCEVAOKwSY79OvpbDaQ==} dependencies: tslib: 2.8.1 + dev: false - '@smithy/config-resolver@3.0.12': + /@smithy/config-resolver@3.0.12: + resolution: {integrity: sha512-YAJP9UJFZRZ8N+UruTeq78zkdjUHmzsY62J4qKWZ4SXB4QXJ/+680EfXXgkYA2xj77ooMqtUY9m406zGNqwivQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/node-config-provider': 3.1.11 '@smithy/types': 3.7.1 '@smithy/util-config-provider': 3.0.0 '@smithy/util-middleware': 3.0.10 tslib: 2.8.1 + dev: false - '@smithy/core@2.5.3': + /@smithy/core@2.5.3: + resolution: {integrity: sha512-96uW8maifUSmehaeW7uydWn7wBc98NEeNI3zN8vqakGpyCQgzyJaA64Z4FCOUmAdCJkhppd/7SZ798Fo4Xx37g==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/middleware-serde': 3.0.10 '@smithy/protocol-http': 4.1.7 @@ -4797,99 +1873,142 @@ snapshots: '@smithy/util-stream': 3.3.1 '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/credential-provider-imds@3.2.7': + /@smithy/credential-provider-imds@3.2.7: + resolution: {integrity: sha512-cEfbau+rrWF8ylkmmVAObOmjbTIzKyUC5TkBL58SbLywD0RCBC4JAUKbmtSm2w5KUJNRPGgpGFMvE2FKnuNlWQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/node-config-provider': 3.1.11 '@smithy/property-provider': 3.1.10 '@smithy/types': 3.7.1 '@smithy/url-parser': 3.0.10 tslib: 2.8.1 + dev: false - '@smithy/eventstream-codec@3.1.9': + /@smithy/eventstream-codec@3.1.9: + resolution: {integrity: sha512-F574nX0hhlNOjBnP+noLtsPFqXnWh2L0+nZKCwcu7P7J8k+k+rdIDs+RMnrMwrzhUE4mwMgyN0cYnEn0G8yrnQ==} dependencies: '@aws-crypto/crc32': 5.2.0 '@smithy/types': 3.7.1 '@smithy/util-hex-encoding': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/eventstream-serde-browser@3.0.13': + /@smithy/eventstream-serde-browser@3.0.13: + resolution: {integrity: sha512-Nee9m+97o9Qj6/XeLz2g2vANS2SZgAxV4rDBMKGHvFJHU/xz88x2RwCkwsvEwYjSX4BV1NG1JXmxEaDUzZTAtw==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/eventstream-serde-universal': 3.0.12 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/eventstream-serde-config-resolver@3.0.10': + /@smithy/eventstream-serde-config-resolver@3.0.10: + resolution: {integrity: sha512-K1M0x7P7qbBUKB0UWIL5KOcyi6zqV5mPJoL0/o01HPJr0CSq3A9FYuJC6e11EX6hR8QTIR++DBiGrYveOu6trw==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/eventstream-serde-node@3.0.12': + /@smithy/eventstream-serde-node@3.0.12: + resolution: {integrity: sha512-kiZymxXvZ4tnuYsPSMUHe+MMfc4FTeFWJIc0Q5wygJoUQM4rVHNghvd48y7ppuulNMbuYt95ah71pYc2+o4JOA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/eventstream-serde-universal': 3.0.12 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/eventstream-serde-universal@3.0.12': + /@smithy/eventstream-serde-universal@3.0.12: + resolution: {integrity: sha512-1i8ifhLJrOZ+pEifTlF0EfZzMLUGQggYQ6WmZ4d5g77zEKf7oZ0kvh1yKWHPjofvOwqrkwRDVuxuYC8wVd662A==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/eventstream-codec': 3.1.9 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/fetch-http-handler@4.1.1': + /@smithy/fetch-http-handler@4.1.1: + resolution: {integrity: sha512-bH7QW0+JdX0bPBadXt8GwMof/jz0H28I84hU1Uet9ISpzUqXqRQ3fEZJ+ANPOhzSEczYvANNl3uDQDYArSFDtA==} dependencies: '@smithy/protocol-http': 4.1.7 '@smithy/querystring-builder': 3.0.10 '@smithy/types': 3.7.1 '@smithy/util-base64': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/hash-blob-browser@3.1.9': + /@smithy/hash-blob-browser@3.1.9: + resolution: {integrity: sha512-wOu78omaUuW5DE+PVWXiRKWRZLecARyP3xcq5SmkXUw9+utgN8HnSnBfrjL2B/4ZxgqPjaAJQkC/+JHf1ITVaQ==} dependencies: '@smithy/chunked-blob-reader': 4.0.0 '@smithy/chunked-blob-reader-native': 3.0.1 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/hash-node@3.0.10': + /@smithy/hash-node@3.0.10: + resolution: {integrity: sha512-3zWGWCHI+FlJ5WJwx73Mw2llYR8aflVyZN5JhoqLxbdPZi6UyKSdCeXAWJw9ja22m6S6Tzz1KZ+kAaSwvydi0g==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 '@smithy/util-buffer-from': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/hash-stream-node@3.1.9': + /@smithy/hash-stream-node@3.1.9: + resolution: {integrity: sha512-3XfHBjSP3oDWxLmlxnt+F+FqXpL3WlXs+XXaB6bV9Wo8BBu87fK1dSEsyH7Z4ZHRmwZ4g9lFMdf08m9hoX1iRA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/invalid-dependency@3.0.10': + /@smithy/invalid-dependency@3.0.10: + resolution: {integrity: sha512-Lp2L65vFi+cj0vFMu2obpPW69DU+6O5g3086lmI4XcnRCG8PxvpWC7XyaVwJCxsZFzueHjXnrOH/E0pl0zikfA==} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/is-array-buffer@2.2.0': + /@smithy/is-array-buffer@2.2.0: + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} dependencies: tslib: 2.8.1 + dev: false - '@smithy/is-array-buffer@3.0.0': + /@smithy/is-array-buffer@3.0.0: + resolution: {integrity: sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==} + engines: {node: '>=16.0.0'} dependencies: tslib: 2.8.1 + dev: false - '@smithy/md5-js@3.0.10': + /@smithy/md5-js@3.0.10: + resolution: {integrity: sha512-m3bv6dApflt3fS2Y1PyWPUtRP7iuBlvikEOGwu0HsCZ0vE7zcIX+dBoh3e+31/rddagw8nj92j0kJg2TfV+SJA==} dependencies: '@smithy/types': 3.7.1 '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/middleware-content-length@3.0.12': + /@smithy/middleware-content-length@3.0.12: + resolution: {integrity: sha512-1mDEXqzM20yywaMDuf5o9ue8OkJ373lSPbaSjyEvkWdqELhFMyNNgKGWL/rCSf4KME8B+HlHKuR8u9kRj8HzEQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/protocol-http': 4.1.7 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/middleware-endpoint@3.2.3': + /@smithy/middleware-endpoint@3.2.3: + resolution: {integrity: sha512-Hdl9296i/EMptaX7agrSzJZDiz5Y8XPUeBbctTmMtnCguGpqfU3jVsTUan0VLaOhsnquqWLL8Bl5HrlbVGT1og==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/core': 2.5.3 '@smithy/middleware-serde': 3.0.10 @@ -4899,8 +2018,11 @@ snapshots: '@smithy/url-parser': 3.0.10 '@smithy/util-middleware': 3.0.10 tslib: 2.8.1 + dev: false - '@smithy/middleware-retry@3.0.27': + /@smithy/middleware-retry@3.0.27: + resolution: {integrity: sha512-H3J/PjJpLL7Tt+fxDKiOD25sMc94YetlQhCnYeNmina2LZscAdu0ZEZPas/kwePHABaEtqp7hqa5S4UJgMs1Tg==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/node-config-provider': 3.1.11 '@smithy/protocol-http': 4.1.7 @@ -4911,63 +2033,96 @@ snapshots: '@smithy/util-retry': 3.0.10 tslib: 2.8.1 uuid: 9.0.1 + dev: false - '@smithy/middleware-serde@3.0.10': + /@smithy/middleware-serde@3.0.10: + resolution: {integrity: sha512-MnAuhh+dD14F428ubSJuRnmRsfOpxSzvRhaGVTvd/lrUDE3kxzCCmH8lnVTvoNQnV2BbJ4c15QwZ3UdQBtFNZA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/middleware-stack@3.0.10': + /@smithy/middleware-stack@3.0.10: + resolution: {integrity: sha512-grCHyoiARDBBGPyw2BeicpjgpsDFWZZxptbVKb3CRd/ZA15F/T6rZjCCuBUjJwdck1nwUuIxYtsS4H9DDpbP5w==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/node-config-provider@3.1.11': + /@smithy/node-config-provider@3.1.11: + resolution: {integrity: sha512-URq3gT3RpDikh/8MBJUB+QGZzfS7Bm6TQTqoh4CqE8NBuyPkWa5eUXj0XFcFfeZVgg3WMh1u19iaXn8FvvXxZw==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/property-provider': 3.1.10 '@smithy/shared-ini-file-loader': 3.1.11 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/node-http-handler@3.3.1': + /@smithy/node-http-handler@3.3.1: + resolution: {integrity: sha512-fr+UAOMGWh6bn4YSEezBCpJn9Ukp9oR4D32sCjCo7U81evE11YePOQ58ogzyfgmjIO79YeOdfXXqr0jyhPQeMg==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/abort-controller': 3.1.8 '@smithy/protocol-http': 4.1.7 '@smithy/querystring-builder': 3.0.10 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/property-provider@3.1.10': + /@smithy/property-provider@3.1.10: + resolution: {integrity: sha512-n1MJZGTorTH2DvyTVj+3wXnd4CzjJxyXeOgnTlgNVFxaaMeT4OteEp4QrzF8p9ee2yg42nvyVK6R/awLCakjeQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/protocol-http@4.1.7': + /@smithy/protocol-http@4.1.7: + resolution: {integrity: sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/querystring-builder@3.0.10': + /@smithy/querystring-builder@3.0.10: + resolution: {integrity: sha512-nT9CQF3EIJtIUepXQuBFb8dxJi3WVZS3XfuDksxSCSn+/CzZowRLdhDn+2acbBv8R6eaJqPupoI/aRFIImNVPQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 '@smithy/util-uri-escape': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/querystring-parser@3.0.10': + /@smithy/querystring-parser@3.0.10: + resolution: {integrity: sha512-Oa0XDcpo9SmjhiDD9ua2UyM3uU01ZTuIrNdZvzwUTykW1PM8o2yJvMh1Do1rY5sUQg4NDV70dMi0JhDx4GyxuQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/service-error-classification@3.0.10': + /@smithy/service-error-classification@3.0.10: + resolution: {integrity: sha512-zHe642KCqDxXLuhs6xmHVgRwy078RfqxP2wRDpIyiF8EmsWXptMwnMwbVa50lw+WOGNrYm9zbaEg0oDe3PTtvQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 + dev: false - '@smithy/shared-ini-file-loader@3.1.11': + /@smithy/shared-ini-file-loader@3.1.11: + resolution: {integrity: sha512-AUdrIZHFtUgmfSN4Gq9nHu3IkHMa1YDcN+s061Nfm+6pQ0mJy85YQDB0tZBCmls0Vuj22pLwDPmL92+Hvfwwlg==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/signature-v4@4.2.3': + /@smithy/signature-v4@4.2.3: + resolution: {integrity: sha512-pPSQQ2v2vu9vc8iew7sszLd0O09I5TRc5zhY71KA+Ao0xYazIG+uLeHbTJfIWGO3BGVLiXjUr3EEeCcEQLjpWQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/is-array-buffer': 3.0.0 '@smithy/protocol-http': 4.1.7 @@ -4977,8 +2132,11 @@ snapshots: '@smithy/util-uri-escape': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/smithy-client@3.4.4': + /@smithy/smithy-client@3.4.4: + resolution: {integrity: sha512-dPGoJuSZqvirBq+yROapBcHHvFjChoAQT8YPWJ820aPHHiowBlB3RL1Q4kPT1hx0qKgJuf+HhyzKi5Gbof4fNA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/core': 2.5.3 '@smithy/middleware-endpoint': 3.2.3 @@ -4987,54 +2145,82 @@ snapshots: '@smithy/types': 3.7.1 '@smithy/util-stream': 3.3.1 tslib: 2.8.1 + dev: false - '@smithy/types@3.7.1': + /@smithy/types@3.7.1: + resolution: {integrity: sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==} + engines: {node: '>=16.0.0'} dependencies: tslib: 2.8.1 + dev: false - '@smithy/url-parser@3.0.10': + /@smithy/url-parser@3.0.10: + resolution: {integrity: sha512-j90NUalTSBR2NaZTuruEgavSdh8MLirf58LoGSk4AtQfyIymogIhgnGUU2Mga2bkMkpSoC9gxb74xBXL5afKAQ==} dependencies: '@smithy/querystring-parser': 3.0.10 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/util-base64@3.0.0': + /@smithy/util-base64@3.0.0: + resolution: {integrity: sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/util-buffer-from': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/util-body-length-browser@3.0.0': + /@smithy/util-body-length-browser@3.0.0: + resolution: {integrity: sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==} dependencies: tslib: 2.8.1 + dev: false - '@smithy/util-body-length-node@3.0.0': + /@smithy/util-body-length-node@3.0.0: + resolution: {integrity: sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==} + engines: {node: '>=16.0.0'} dependencies: tslib: 2.8.1 + dev: false - '@smithy/util-buffer-from@2.2.0': + /@smithy/util-buffer-from@2.2.0: + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} dependencies: '@smithy/is-array-buffer': 2.2.0 tslib: 2.8.1 + dev: false - '@smithy/util-buffer-from@3.0.0': + /@smithy/util-buffer-from@3.0.0: + resolution: {integrity: sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/is-array-buffer': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/util-config-provider@3.0.0': + /@smithy/util-config-provider@3.0.0: + resolution: {integrity: sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==} + engines: {node: '>=16.0.0'} dependencies: tslib: 2.8.1 + dev: false - '@smithy/util-defaults-mode-browser@3.0.27': + /@smithy/util-defaults-mode-browser@3.0.27: + resolution: {integrity: sha512-GV8NvPy1vAGp7u5iD/xNKUxCorE4nQzlyl057qRac+KwpH5zq8wVq6rE3lPPeuFLyQXofPN6JwxL1N9ojGapiQ==} + engines: {node: '>= 10.0.0'} dependencies: '@smithy/property-provider': 3.1.10 '@smithy/smithy-client': 3.4.4 '@smithy/types': 3.7.1 bowser: 2.11.0 tslib: 2.8.1 + dev: false - '@smithy/util-defaults-mode-node@3.0.27': + /@smithy/util-defaults-mode-node@3.0.27: + resolution: {integrity: sha512-7+4wjWfZqZxZVJvDutO+i1GvL6bgOajEkop4FuR6wudFlqBiqwxw3HoH6M9NgeCd37km8ga8NPp2JacQEtAMPg==} + engines: {node: '>= 10.0.0'} dependencies: '@smithy/config-resolver': 3.0.12 '@smithy/credential-provider-imds': 3.2.7 @@ -5043,29 +2229,44 @@ snapshots: '@smithy/smithy-client': 3.4.4 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/util-endpoints@2.1.6': + /@smithy/util-endpoints@2.1.6: + resolution: {integrity: sha512-mFV1t3ndBh0yZOJgWxO9J/4cHZVn5UG1D8DeCc6/echfNkeEJWu9LD7mgGH5fHrEdR7LDoWw7PQO6QiGpHXhgA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/node-config-provider': 3.1.11 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/util-hex-encoding@3.0.0': + /@smithy/util-hex-encoding@3.0.0: + resolution: {integrity: sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==} + engines: {node: '>=16.0.0'} dependencies: tslib: 2.8.1 + dev: false - '@smithy/util-middleware@3.0.10': + /@smithy/util-middleware@3.0.10: + resolution: {integrity: sha512-eJO+/+RsrG2RpmY68jZdwQtnfsxjmPxzMlQpnHKjFPwrYqvlcT+fHdT+ZVwcjlWSrByOhGr9Ff2GG17efc192A==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/util-retry@3.0.10': + /@smithy/util-retry@3.0.10: + resolution: {integrity: sha512-1l4qatFp4PiU6j7UsbasUHL2VU023NRB/gfaa1M0rDqVrRN4g3mCArLRyH3OuktApA4ye+yjWQHjdziunw2eWA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/service-error-classification': 3.0.10 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/util-stream@3.3.1': + /@smithy/util-stream@3.3.1: + resolution: {integrity: sha512-Ff68R5lJh2zj+AUTvbAU/4yx+6QPRzg7+pI7M1FbtQHcRIp7xvguxVsQBKyB3fwiOwhAKu0lnNyYBaQfSW6TNw==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/fetch-http-handler': 4.1.1 '@smithy/node-http-handler': 3.3.1 @@ -5075,291 +2276,443 @@ snapshots: '@smithy/util-hex-encoding': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/util-uri-escape@3.0.0': + /@smithy/util-uri-escape@3.0.0: + resolution: {integrity: sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==} + engines: {node: '>=16.0.0'} dependencies: tslib: 2.8.1 + dev: false - '@smithy/util-utf8@2.3.0': + /@smithy/util-utf8@2.3.0: + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} dependencies: '@smithy/util-buffer-from': 2.2.0 tslib: 2.8.1 + dev: false - '@smithy/util-utf8@3.0.0': + /@smithy/util-utf8@3.0.0: + resolution: {integrity: sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/util-buffer-from': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/util-waiter@3.1.9': + /@smithy/util-waiter@3.1.9: + resolution: {integrity: sha512-/aMXPANhMOlMPjfPtSrDfPeVP8l56SJlz93xeiLmhLe5xvlXA5T3abZ2ilEsDEPeY9T/wnN/vNGn9wa1SbufWA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/abort-controller': 3.1.8 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@sqltools/formatter@1.2.5': {} + /@sqltools/formatter@1.2.5: + resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} + dev: false - '@tsconfig/node10@1.0.11': {} + /@tsconfig/node10@1.0.11: + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} - '@tsconfig/node12@1.0.11': {} + /@tsconfig/node12@1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - '@tsconfig/node14@1.0.3': {} + /@tsconfig/node14@1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - '@tsconfig/node16@1.0.4': {} + /@tsconfig/node16@1.0.4: + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - '@types/babel__core@7.20.5': + /@types/babel__core@7.20.5: + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} dependencies: '@babel/parser': 7.26.2 '@babel/types': 7.26.0 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.6 + dev: true - '@types/babel__generator@7.6.8': + /@types/babel__generator@7.6.8: + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} dependencies: '@babel/types': 7.26.0 + dev: true - '@types/babel__template@7.4.4': + /@types/babel__template@7.4.4: + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} dependencies: '@babel/parser': 7.26.2 '@babel/types': 7.26.0 + dev: true - '@types/babel__traverse@7.20.6': + /@types/babel__traverse@7.20.6: + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} dependencies: '@babel/types': 7.26.0 + dev: true - '@types/body-parser@1.19.5': + /@types/body-parser@1.19.5: + resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} dependencies: '@types/connect': 3.4.38 - '@types/node': 20.17.6 + '@types/node': 20.3.1 + dev: true - '@types/connect@3.4.38': + /@types/connect@3.4.38: + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} dependencies: - '@types/node': 20.17.6 + '@types/node': 20.3.1 + dev: true - '@types/cookiejar@2.1.5': {} + /@types/cookiejar@2.1.5: + resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} + dev: true - '@types/eslint-scope@3.7.7': + /@types/eslint-scope@3.7.7: + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} dependencies: '@types/eslint': 9.6.1 '@types/estree': 1.0.6 + dev: true - '@types/eslint@9.6.1': + /@types/eslint@9.6.1: + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} dependencies: '@types/estree': 1.0.6 '@types/json-schema': 7.0.15 + dev: true - '@types/estree@1.0.6': {} + /@types/estree@1.0.6: + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + dev: true - '@types/express-serve-static-core@5.0.1': + /@types/express-serve-static-core@5.0.1: + resolution: {integrity: sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==} dependencies: - '@types/node': 20.17.6 + '@types/node': 20.3.1 '@types/qs': 6.9.17 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 + dev: true - '@types/express@5.0.0': + /@types/express@5.0.0: + resolution: {integrity: sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==} dependencies: '@types/body-parser': 1.19.5 '@types/express-serve-static-core': 5.0.1 '@types/qs': 6.9.17 '@types/serve-static': 1.15.7 + dev: true - '@types/graceful-fs@4.1.9': + /@types/graceful-fs@4.1.9: + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} dependencies: - '@types/node': 20.17.6 + '@types/node': 20.3.1 + dev: true - '@types/http-errors@2.0.4': {} + /@types/http-errors@2.0.4: + resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} + dev: true - '@types/istanbul-lib-coverage@2.0.6': {} + /@types/istanbul-lib-coverage@2.0.6: + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + dev: true - '@types/istanbul-lib-report@3.0.3': + /@types/istanbul-lib-report@3.0.3: + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} dependencies: '@types/istanbul-lib-coverage': 2.0.6 + dev: true - '@types/istanbul-reports@3.0.4': + /@types/istanbul-reports@3.0.4: + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} dependencies: '@types/istanbul-lib-report': 3.0.3 + dev: true - '@types/jest@29.5.14': + /@types/jest@29.5.2: + resolution: {integrity: sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg==} dependencies: expect: 29.7.0 pretty-format: 29.7.0 + dev: true - '@types/json-schema@7.0.15': {} + /@types/json-schema@7.0.15: + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + dev: true - '@types/jsonwebtoken@9.0.5': + /@types/jsonwebtoken@9.0.5: + resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==} dependencies: - '@types/node': 20.17.6 + '@types/node': 20.3.1 + dev: false - '@types/methods@1.1.4': {} + /@types/methods@1.1.4: + resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} + dev: true - '@types/mime@1.3.5': {} + /@types/mime@1.3.5: + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + dev: true - '@types/multer@1.4.12': + /@types/multer@1.4.12: + resolution: {integrity: sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==} dependencies: '@types/express': 5.0.0 + dev: true - '@types/node@20.17.6': - dependencies: - undici-types: 6.19.8 + /@types/node@20.3.1: + resolution: {integrity: sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==} + + /@types/parse-json@4.0.2: + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + dev: true - '@types/qs@6.9.17': {} + /@types/qs@6.9.17: + resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==} + dev: true - '@types/range-parser@1.2.7': {} + /@types/range-parser@1.2.7: + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + dev: true - '@types/send@0.17.4': + /@types/send@0.17.4: + resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} dependencies: '@types/mime': 1.3.5 - '@types/node': 20.17.6 + '@types/node': 20.3.1 + dev: true - '@types/serve-static@1.15.7': + /@types/serve-static@1.15.7: + resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} dependencies: '@types/http-errors': 2.0.4 - '@types/node': 20.17.6 + '@types/node': 20.3.1 '@types/send': 0.17.4 + dev: true - '@types/stack-utils@2.0.3': {} + /@types/stack-utils@2.0.3: + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + dev: true - '@types/superagent@8.1.9': + /@types/superagent@8.1.9: + resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==} dependencies: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 - '@types/node': 20.17.6 + '@types/node': 20.3.1 form-data: 4.0.1 + dev: true - '@types/supertest@6.0.2': + /@types/supertest@6.0.0: + resolution: {integrity: sha512-j3/Z2avY+H3yn+xp/ef//QyqqE+dg3rWh14Ewi/QZs6uVK+oOs7lFRXtjp2YHAqHJZ4OFGNmCxZO5vd7AuG/Dg==} dependencies: + '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 '@types/superagent': 8.1.9 + dev: true - '@types/validator@13.12.2': {} + /@types/validator@13.12.2: + resolution: {integrity: sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==} - '@types/yargs-parser@21.0.3': {} + /@types/yargs-parser@21.0.3: + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + dev: true - '@types/yargs@17.0.33': + /@types/yargs@17.0.33: + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} dependencies: '@types/yargs-parser': 21.0.3 + dev: true - '@typescript-eslint/eslint-plugin@8.13.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)': + /@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0)(eslint@8.0.0)(typescript@5.1.3): + resolution: {integrity: sha512-STIZdwEQRXAHvNUS6ILDf5z3u95Gc8jzywunxSNqX00OooIemaaNIA0vEgynJlycL5AjabYLLrIyHd4iazyvtg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.13.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/scope-manager': 8.13.0 - '@typescript-eslint/type-utils': 8.13.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/utils': 8.13.0(eslint@8.57.1)(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 8.13.0 - eslint: 8.57.1 + '@typescript-eslint/parser': 8.0.0(eslint@8.0.0)(typescript@5.1.3) + '@typescript-eslint/scope-manager': 8.0.0 + '@typescript-eslint/type-utils': 8.0.0(eslint@8.0.0)(typescript@5.1.3) + '@typescript-eslint/utils': 8.0.0(eslint@8.0.0)(typescript@5.1.3) + '@typescript-eslint/visitor-keys': 8.0.0 + eslint: 8.0.0 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.4.0(typescript@5.6.3) - optionalDependencies: - typescript: 5.6.3 + ts-api-utils: 1.4.0(typescript@5.1.3) + typescript: 5.1.3 transitivePeerDependencies: - supports-color + dev: true - '@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.3)': + /@typescript-eslint/parser@8.0.0(eslint@8.0.0)(typescript@5.1.3): + resolution: {integrity: sha512-pS1hdZ+vnrpDIxuFXYQpLTILglTjSYJ9MbetZctrUawogUsPdz31DIIRZ9+rab0LhYNTsk88w4fIzVheiTbWOQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true dependencies: - '@typescript-eslint/scope-manager': 8.13.0 - '@typescript-eslint/types': 8.13.0 - '@typescript-eslint/typescript-estree': 8.13.0(typescript@5.6.3) - '@typescript-eslint/visitor-keys': 8.13.0 + '@typescript-eslint/scope-manager': 8.0.0 + '@typescript-eslint/types': 8.0.0 + '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.1.3) + '@typescript-eslint/visitor-keys': 8.0.0 debug: 4.3.7 - eslint: 8.57.1 - optionalDependencies: - typescript: 5.6.3 + eslint: 8.0.0 + typescript: 5.1.3 transitivePeerDependencies: - supports-color + dev: true - '@typescript-eslint/scope-manager@8.13.0': + /@typescript-eslint/scope-manager@8.0.0: + resolution: {integrity: sha512-V0aa9Csx/ZWWv2IPgTfY7T4agYwJyILESu/PVqFtTFz9RIS823mAze+NbnBI8xiwdX3iqeQbcTYlvB04G9wyQw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: - '@typescript-eslint/types': 8.13.0 - '@typescript-eslint/visitor-keys': 8.13.0 + '@typescript-eslint/types': 8.0.0 + '@typescript-eslint/visitor-keys': 8.0.0 + dev: true - '@typescript-eslint/type-utils@8.13.0(eslint@8.57.1)(typescript@5.6.3)': + /@typescript-eslint/type-utils@8.0.0(eslint@8.0.0)(typescript@5.1.3): + resolution: {integrity: sha512-mJAFP2mZLTBwAn5WI4PMakpywfWFH5nQZezUQdSKV23Pqo6o9iShQg1hP2+0hJJXP2LnZkWPphdIq4juYYwCeg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true dependencies: - '@typescript-eslint/typescript-estree': 8.13.0(typescript@5.6.3) - '@typescript-eslint/utils': 8.13.0(eslint@8.57.1)(typescript@5.6.3) + '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.1.3) + '@typescript-eslint/utils': 8.0.0(eslint@8.0.0)(typescript@5.1.3) debug: 4.3.7 - ts-api-utils: 1.4.0(typescript@5.6.3) - optionalDependencies: - typescript: 5.6.3 + ts-api-utils: 1.4.0(typescript@5.1.3) + typescript: 5.1.3 transitivePeerDependencies: - eslint - supports-color + dev: true - '@typescript-eslint/types@8.13.0': {} + /@typescript-eslint/types@8.0.0: + resolution: {integrity: sha512-wgdSGs9BTMWQ7ooeHtu5quddKKs5Z5dS+fHLbrQI+ID0XWJLODGMHRfhwImiHoeO2S5Wir2yXuadJN6/l4JRxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: true - '@typescript-eslint/typescript-estree@8.13.0(typescript@5.6.3)': + /@typescript-eslint/typescript-estree@8.0.0(typescript@5.1.3): + resolution: {integrity: sha512-5b97WpKMX+Y43YKi4zVcCVLtK5F98dFls3Oxui8LbnmRsseKenbbDinmvxrWegKDMmlkIq/XHuyy0UGLtpCDKg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true dependencies: - '@typescript-eslint/types': 8.13.0 - '@typescript-eslint/visitor-keys': 8.13.0 + '@typescript-eslint/types': 8.0.0 + '@typescript-eslint/visitor-keys': 8.0.0 debug: 4.3.7 - fast-glob: 3.3.2 + globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 1.4.0(typescript@5.6.3) - optionalDependencies: - typescript: 5.6.3 + ts-api-utils: 1.4.0(typescript@5.1.3) + typescript: 5.1.3 transitivePeerDependencies: - supports-color + dev: true - '@typescript-eslint/utils@8.13.0(eslint@8.57.1)(typescript@5.6.3)': + /@typescript-eslint/utils@8.0.0(eslint@8.0.0)(typescript@5.1.3): + resolution: {integrity: sha512-k/oS/A/3QeGLRvOWCg6/9rATJL5rec7/5s1YmdS0ZU6LHveJyGFwBvLhSRBv6i9xaj7etmosp+l+ViN1I9Aj/Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) - '@typescript-eslint/scope-manager': 8.13.0 - '@typescript-eslint/types': 8.13.0 - '@typescript-eslint/typescript-estree': 8.13.0(typescript@5.6.3) - eslint: 8.57.1 + '@eslint-community/eslint-utils': 4.4.1(eslint@8.0.0) + '@typescript-eslint/scope-manager': 8.0.0 + '@typescript-eslint/types': 8.0.0 + '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.1.3) + eslint: 8.0.0 transitivePeerDependencies: - supports-color - typescript + dev: true - '@typescript-eslint/visitor-keys@8.13.0': + /@typescript-eslint/visitor-keys@8.0.0: + resolution: {integrity: sha512-oN0K4nkHuOyF3PVMyETbpP5zp6wfyOvm7tWhTMfoqxSSsPmJIh6JNASuZDlODE8eE+0EB9uar+6+vxr9DBTYOA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: - '@typescript-eslint/types': 8.13.0 + '@typescript-eslint/types': 8.0.0 eslint-visitor-keys: 3.4.3 + dev: true - '@ungap/structured-clone@1.2.0': {} - - '@webassemblyjs/ast@1.14.1': + /@webassemblyjs/ast@1.14.1: + resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} dependencies: '@webassemblyjs/helper-numbers': 1.13.2 '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + dev: true - '@webassemblyjs/floating-point-hex-parser@1.13.2': {} + /@webassemblyjs/floating-point-hex-parser@1.13.2: + resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + dev: true - '@webassemblyjs/helper-api-error@1.13.2': {} + /@webassemblyjs/helper-api-error@1.13.2: + resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + dev: true - '@webassemblyjs/helper-buffer@1.14.1': {} + /@webassemblyjs/helper-buffer@1.14.1: + resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + dev: true - '@webassemblyjs/helper-numbers@1.13.2': + /@webassemblyjs/helper-numbers@1.13.2: + resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} dependencies: '@webassemblyjs/floating-point-hex-parser': 1.13.2 '@webassemblyjs/helper-api-error': 1.13.2 '@xtuc/long': 4.2.2 + dev: true - '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} + /@webassemblyjs/helper-wasm-bytecode@1.13.2: + resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + dev: true - '@webassemblyjs/helper-wasm-section@1.14.1': + /@webassemblyjs/helper-wasm-section@1.14.1: + resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} dependencies: '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/helper-buffer': 1.14.1 '@webassemblyjs/helper-wasm-bytecode': 1.13.2 '@webassemblyjs/wasm-gen': 1.14.1 + dev: true - '@webassemblyjs/ieee754@1.13.2': + /@webassemblyjs/ieee754@1.13.2: + resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} dependencies: '@xtuc/ieee754': 1.2.0 + dev: true - '@webassemblyjs/leb128@1.13.2': + /@webassemblyjs/leb128@1.13.2: + resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} dependencies: '@xtuc/long': 4.2.2 + dev: true - '@webassemblyjs/utf8@1.13.2': {} + /@webassemblyjs/utf8@1.13.2: + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + dev: true - '@webassemblyjs/wasm-edit@1.14.1': + /@webassemblyjs/wasm-edit@1.14.1: + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} dependencies: '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/helper-buffer': 1.14.1 @@ -5369,23 +2722,29 @@ snapshots: '@webassemblyjs/wasm-opt': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 '@webassemblyjs/wast-printer': 1.14.1 + dev: true - '@webassemblyjs/wasm-gen@1.14.1': + /@webassemblyjs/wasm-gen@1.14.1: + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} dependencies: '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/helper-wasm-bytecode': 1.13.2 '@webassemblyjs/ieee754': 1.13.2 '@webassemblyjs/leb128': 1.13.2 '@webassemblyjs/utf8': 1.13.2 + dev: true - '@webassemblyjs/wasm-opt@1.14.1': + /@webassemblyjs/wasm-opt@1.14.1: + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} dependencies: '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/helper-buffer': 1.14.1 '@webassemblyjs/wasm-gen': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 + dev: true - '@webassemblyjs/wasm-parser@1.14.1': + /@webassemblyjs/wasm-parser@1.14.1: + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} dependencies: '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/helper-api-error': 1.13.2 @@ -5393,109 +2752,204 @@ snapshots: '@webassemblyjs/ieee754': 1.13.2 '@webassemblyjs/leb128': 1.13.2 '@webassemblyjs/utf8': 1.13.2 + dev: true - '@webassemblyjs/wast-printer@1.14.1': + /@webassemblyjs/wast-printer@1.14.1: + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} dependencies: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 + dev: true - '@xtuc/ieee754@1.2.0': {} + /@xtuc/ieee754@1.2.0: + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + dev: true - '@xtuc/long@4.2.2': {} + /@xtuc/long@4.2.2: + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + dev: true - accepts@1.3.8: + /accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} dependencies: mime-types: 2.1.35 negotiator: 0.6.3 - acorn-jsx@5.3.2(acorn@8.14.0): + /acorn-import-assertions@1.9.0(acorn@8.14.0): + resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} + deprecated: package has been renamed to acorn-import-attributes + peerDependencies: + acorn: ^8 + dependencies: + acorn: 8.14.0 + dev: true + + /acorn-jsx@5.3.2(acorn@8.14.0): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: acorn: 8.14.0 + dev: true - acorn-walk@8.3.4: + /acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} dependencies: acorn: 8.14.0 - acorn@8.14.0: {} + /acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true - ajv-formats@2.1.1(ajv@8.12.0): - optionalDependencies: + /ajv-formats@2.1.1(ajv@8.12.0): + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + dependencies: ajv: 8.12.0 + dev: true - ajv-keywords@3.5.2(ajv@6.12.6): + /ajv-keywords@3.5.2(ajv@6.12.6): + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 dependencies: ajv: 6.12.6 + dev: true - ajv@6.12.6: + /ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 + dev: true - ajv@8.12.0: + /ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} dependencies: fast-deep-equal: 3.1.3 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 uri-js: 4.4.1 + dev: true - ansi-colors@4.1.3: {} + /ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + dev: true - ansi-escapes@4.3.2: + /ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} dependencies: type-fest: 0.21.3 + dev: true - ansi-regex@5.0.1: {} + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} - ansi-regex@6.1.0: {} + /ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + dev: false - ansi-styles@4.3.0: + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} dependencies: color-convert: 2.0.1 - ansi-styles@5.2.0: {} + /ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true - ansi-styles@6.2.1: {} + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + dev: false - any-promise@1.3.0: {} + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: false - anymatch@3.1.3: + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 + dev: true - app-root-path@3.1.0: {} + /app-root-path@3.1.0: + resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} + engines: {node: '>= 6.0.0'} + dev: false - append-field@1.0.0: {} + /append-field@1.0.0: + resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} - arg@4.1.3: {} + /arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - argon2@0.41.1: + /argon2@0.41.1: + resolution: {integrity: sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==} + engines: {node: '>=16.17.0'} + requiresBuild: true dependencies: '@phc/format': 1.0.0 node-addon-api: 8.2.2 node-gyp-build: 4.8.2 + dev: false - argparse@1.0.10: + /argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} dependencies: sprintf-js: 1.0.3 + dev: true - argparse@2.0.1: {} + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - array-flatten@1.1.1: {} + /array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - array-timsort@1.0.3: {} + /array-timsort@1.0.3: + resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + dev: true - asap@2.0.6: {} + /array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true - async@3.2.6: {} + /asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + dev: true - asynckit@0.4.0: {} + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: true - aws-ssl-profiles@1.1.2: {} + /aws-ssl-profiles@1.1.2: + resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} + engines: {node: '>= 6.0.0'} + dev: false - babel-jest@29.7.0(@babel/core@7.26.0): + /babel-jest@29.7.0(@babel/core@7.26.0): + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 dependencies: '@babel/core': 7.26.0 '@jest/transform': 29.7.0 @@ -5507,8 +2961,11 @@ snapshots: slash: 3.0.0 transitivePeerDependencies: - supports-color + dev: true - babel-plugin-istanbul@6.1.1: + /babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} dependencies: '@babel/helper-plugin-utils': 7.25.9 '@istanbuljs/load-nyc-config': 1.1.0 @@ -5517,15 +2974,22 @@ snapshots: test-exclude: 6.0.0 transitivePeerDependencies: - supports-color + dev: true - babel-plugin-jest-hoist@29.6.3: + /babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/template': 7.25.9 '@babel/types': 7.26.0 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.6 + dev: true - babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.0): + /babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.0): + resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} + peerDependencies: + '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.26.0 '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.26.0) @@ -5543,26 +3007,41 @@ snapshots: '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0) '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.0) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.0) + dev: true - babel-preset-jest@29.6.3(@babel/core@7.26.0): + /babel-preset-jest@29.6.3(@babel/core@7.26.0): + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.26.0 babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.0) + dev: true - balanced-match@1.0.2: {} + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - base64-js@1.5.1: {} + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - binary-extensions@2.3.0: {} + /binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + dev: true - bl@4.1.0: + /bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} dependencies: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 + dev: true - body-parser@1.20.3: + /body-parser@1.20.1: + resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} dependencies: bytes: 3.1.2 content-type: 1.0.5 @@ -5572,64 +3051,112 @@ snapshots: http-errors: 2.0.0 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.13.0 + qs: 6.11.0 + raw-body: 2.5.1 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + /body-parser@1.20.2: + resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.11.0 raw-body: 2.5.2 type-is: 1.6.18 unpipe: 1.0.0 transitivePeerDependencies: - supports-color - bowser@2.11.0: {} + /bowser@2.11.0: + resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + dev: false - brace-expansion@1.1.11: + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 + dev: true - brace-expansion@2.0.1: + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 - braces@3.0.3: + /braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} dependencies: fill-range: 7.1.1 - browserslist@4.24.2: + /browserslist@4.24.2: + resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true dependencies: caniuse-lite: 1.0.30001679 electron-to-chromium: 1.5.55 node-releases: 2.0.18 update-browserslist-db: 1.1.1(browserslist@4.24.2) + dev: true - bs-logger@0.2.6: + /bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} dependencies: fast-json-stable-stringify: 2.1.0 + dev: true - bser@2.1.1: + /bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} dependencies: node-int64: 0.4.0 + dev: true - buffer-equal-constant-time@1.0.1: {} + /buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + dev: false - buffer-from@1.1.2: {} + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - buffer@5.7.1: + /buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + dev: true - buffer@6.0.3: + /buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + dev: false - busboy@1.6.0: + /busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} dependencies: streamsearch: 1.1.0 - bytes@3.1.2: {} + /bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} - call-bind@1.0.7: + /call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} dependencies: es-define-property: 1.0.0 es-errors: 1.3.0 @@ -5637,26 +3164,44 @@ snapshots: get-intrinsic: 1.2.4 set-function-length: 1.2.2 - callsites@3.1.0: {} + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true - camelcase@5.3.1: {} + /camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + dev: true - camelcase@6.3.0: {} + /camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + dev: true - caniuse-lite@1.0.30001679: {} + /caniuse-lite@1.0.30001679: + resolution: {integrity: sha512-j2YqID/YwpLnKzCmBOS4tlZdWprXm3ZmQLBH9ZBXFOhoxLA46fwyBvx6toCBWBmnuwUY/qB3kEU6gFx8qgCroA==} + dev: true - chalk@4.1.2: + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.3.0: {} - - char-regex@1.0.2: {} + /char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + dev: true - chardet@0.7.0: {} + /chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + dev: true - chokidar@3.6.0: + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} dependencies: anymatch: 3.1.3 braces: 3.0.3 @@ -5667,26 +3212,43 @@ snapshots: readdirp: 3.6.0 optionalDependencies: fsevents: 2.3.3 + dev: true - chrome-trace-event@1.0.4: {} + /chrome-trace-event@1.0.4: + resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} + engines: {node: '>=6.0'} + dev: true - ci-info@3.9.0: {} + /ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + dev: true - cjs-module-lexer@1.4.1: {} + /cjs-module-lexer@1.4.1: + resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} + dev: true - class-transformer@0.5.1: {} + /class-transformer@0.5.1: + resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} - class-validator@0.14.1: + /class-validator@0.14.1: + resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==} dependencies: '@types/validator': 13.12.2 - libphonenumber-js: 1.11.13 + libphonenumber-js: 1.11.14 validator: 13.12.0 - cli-cursor@3.1.0: + /cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} dependencies: restore-cursor: 3.1.0 + dev: true - cli-highlight@2.1.11: + /cli-highlight@2.1.11: + resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true dependencies: chalk: 4.1.2 highlight.js: 10.7.3 @@ -5694,111 +3256,174 @@ snapshots: parse5: 5.1.1 parse5-htmlparser2-tree-adapter: 6.0.1 yargs: 16.2.0 + dev: false - cli-spinners@2.9.2: {} + /cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + dev: true - cli-table3@0.6.5: + /cli-table3@0.6.3: + resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==} + engines: {node: 10.* || >= 12.*} dependencies: string-width: 4.2.3 optionalDependencies: '@colors/colors': 1.5.0 + dev: true - cli-width@3.0.0: {} - - cli-width@4.1.0: {} + /cli-width@3.0.0: + resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} + engines: {node: '>= 10'} + dev: true - cliui@7.0.4: + /cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + dev: false - cliui@8.0.1: + /cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - clone@1.0.4: {} + /clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + dev: true - co@4.6.0: {} + /co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + dev: true - collect-v8-coverage@1.0.2: {} + /collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + dev: true - color-convert@2.0.1: + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 - color-name@1.1.4: {} + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - combined-stream@1.0.8: + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 + dev: true - commander@2.20.3: {} + /commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + dev: true - commander@4.1.1: {} + /commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + dev: true - comment-json@4.2.5: + /comment-json@4.2.3: + resolution: {integrity: sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==} + engines: {node: '>= 6'} dependencies: array-timsort: 1.0.3 core-util-is: 1.0.3 esprima: 4.0.1 has-own-prop: 2.0.0 repeat-string: 1.6.1 + dev: true - component-emitter@1.3.1: {} + /component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + dev: true - concat-map@0.0.1: {} + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true - concat-stream@1.6.2: + /concat-stream@1.6.2: + resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} + engines: {'0': node >= 0.8} dependencies: buffer-from: 1.1.2 inherits: 2.0.4 readable-stream: 2.3.8 typedarray: 0.0.6 - consola@2.15.3: {} + /consola@2.15.3: + resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} - consola@3.2.3: {} + /consola@3.2.3: + resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} + engines: {node: ^14.18.0 || >=16.10.0} + dev: false - content-disposition@0.5.4: + /content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} dependencies: safe-buffer: 5.2.1 - content-type@1.0.5: {} + /content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} - convert-source-map@2.0.0: {} + /convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + dev: true - cookie-signature@1.0.6: {} + /cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - cookie@0.7.1: {} + /cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} - cookiejar@2.1.4: {} + /cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + dev: true - core-util-is@1.0.3: {} + /core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - cors@2.8.5: + /cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} dependencies: object-assign: 4.1.1 vary: 1.1.2 - cosmiconfig@8.3.6(typescript@5.6.3): + /cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} dependencies: + '@types/parse-json': 4.0.2 import-fresh: 3.3.0 - js-yaml: 4.1.0 parse-json: 5.2.0 path-type: 4.0.0 - optionalDependencies: - typescript: 5.6.3 + yaml: 1.10.2 + dev: true - create-jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): + /create-jest@29.7.0(@types/node@20.3.1)(ts-node@10.9.1): + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -5806,188 +3431,352 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + dev: true - create-require@1.1.1: {} + /create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - cross-spawn@7.0.5: + /cross-spawn@7.0.5: + resolution: {integrity: sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==} + engines: {node: '>= 8'} dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - dayjs@1.11.13: {} + /dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + dev: false - debug@2.6.9: + /debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true dependencies: ms: 2.0.0 - debug@4.3.7: + /debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true dependencies: ms: 2.1.3 - dedent@1.5.3: {} + /dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + dev: true - deep-is@0.1.4: {} + /deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true - deepmerge@4.3.1: {} + /deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + dev: true - defaults@1.0.4: + /defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} dependencies: clone: 1.0.4 + dev: true - define-data-property@1.1.4: + /define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} dependencies: es-define-property: 1.0.0 es-errors: 1.3.0 gopd: 1.0.1 - delayed-stream@1.0.0: {} + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: true - denque@2.1.0: {} + /denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + dev: false - depd@2.0.0: {} + /depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} - destr@2.0.3: {} + /destr@2.0.3: + resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} + dev: false - destroy@1.2.0: {} + /destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - detect-newline@3.1.0: {} + /detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + dev: true - dezalgo@1.0.4: + /dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} dependencies: asap: 2.0.6 wrappy: 1.0.2 + dev: true + + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true - diff-sequences@29.6.3: {} + /diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} - diff@4.0.2: {} + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + dependencies: + path-type: 4.0.0 + dev: true - doctrine@3.0.0: + /doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} dependencies: esutils: 2.0.3 + dev: true - dotenv-expand@10.0.0: {} + /dotenv-expand@10.0.0: + resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} + engines: {node: '>=12'} + dev: false - dotenv@16.4.5: {} + /dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + dev: false - eastasianwidth@0.2.0: {} + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: false - ebec@1.1.1: + /ebec@1.1.1: + resolution: {integrity: sha512-JZ1vcvPQtR+8LGbZmbjG21IxLQq/v47iheJqn2F6yB2CgnGfn8ZVg3myHrf3buIZS8UCwQK0jOSIb3oHX7aH8g==} dependencies: smob: 1.5.0 + dev: false - ebec@2.3.0: {} + /ebec@2.3.0: + resolution: {integrity: sha512-bt+0tSL7223VU3PSVi0vtNLZ8pO1AfWolcPPMk2a/a5H+o/ZU9ky0n3A0zhrR4qzJTN61uPsGIO4ShhOukdzxA==} + dev: false - ecdsa-sig-formatter@1.0.11: + /ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} dependencies: safe-buffer: 5.2.1 + dev: false - ee-first@1.1.1: {} - - ejs@3.1.10: - dependencies: - jake: 10.9.2 + /ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.55: {} + /electron-to-chromium@1.5.55: + resolution: {integrity: sha512-6maZ2ASDOTBtjt9FhqYPRnbvKU5tjG0IN9SztUOWYw2AzNDNpKJYLJmlK0/En4Hs/aiWnB+JZ+gW19PIGszgKg==} + dev: true - emittery@0.13.1: {} + /emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + dev: true - emoji-regex@8.0.0: {} + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - emoji-regex@9.2.2: {} + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: false - encodeurl@1.0.2: {} + /encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} - encodeurl@2.0.0: {} + /end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} + dependencies: + once: 1.4.0 + dev: true - enhanced-resolve@5.17.1: + /enhanced-resolve@5.17.1: + resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} + engines: {node: '>=10.13.0'} dependencies: graceful-fs: 4.2.11 tapable: 2.2.1 + dev: true + + /enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + dev: true - envix@1.5.0: + /envix@1.5.0: + resolution: {integrity: sha512-IOxTKT+tffjxgvX2O5nq6enbkv6kBQ/QdMy18bZWo0P0rKPvsRp2/EypIPwTvJfnmk3VdOlq/KcRSZCswefM/w==} + engines: {node: '>=18.0.0'} dependencies: - std-env: 3.7.0 + std-env: 3.8.0 + dev: false - error-ex@1.3.2: + /error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: is-arrayish: 0.2.1 + dev: true - es-define-property@1.0.0: + /es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} dependencies: get-intrinsic: 1.2.4 - es-errors@1.3.0: {} + /es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} - es-module-lexer@1.5.4: {} + /es-module-lexer@1.5.4: + resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + dev: true - escalade@3.2.0: {} + /escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} - escape-html@1.0.3: {} + /escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - escape-string-regexp@1.0.5: {} + /escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + dev: true - escape-string-regexp@2.0.0: {} + /escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + dev: true - escape-string-regexp@4.0.0: {} + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true - eslint-config-prettier@9.1.0(eslint@8.57.1): + /eslint-config-prettier@9.0.0(eslint@8.0.0): + resolution: {integrity: sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' dependencies: - eslint: 8.57.1 + eslint: 8.0.0 + dev: true - eslint-plugin-prettier@5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.3.3): + /eslint-plugin-prettier@5.0.0(eslint-config-prettier@9.0.0)(eslint@8.0.0)(prettier@3.0.0): + resolution: {integrity: sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '*' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true dependencies: - eslint: 8.57.1 - prettier: 3.3.3 + eslint: 8.0.0 + eslint-config-prettier: 9.0.0(eslint@8.0.0) + prettier: 3.0.0 prettier-linter-helpers: 1.0.0 - synckit: 0.9.2 - optionalDependencies: - '@types/eslint': 9.6.1 - eslint-config-prettier: 9.1.0(eslint@8.57.1) + synckit: 0.8.8 + dev: true - eslint-scope@5.1.1: + /eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} dependencies: esrecurse: 4.3.0 estraverse: 4.3.0 + dev: true - eslint-scope@7.2.2: + /eslint-scope@6.0.0: + resolution: {integrity: sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 + dev: true - eslint-visitor-keys@3.4.3: {} + /eslint-utils@3.0.0(eslint@8.0.0): + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + dependencies: + eslint: 8.0.0 + eslint-visitor-keys: 2.1.0 + dev: true + + /eslint-visitor-keys@2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + dev: true - eslint@8.57.1: + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true + + /eslint@8.0.0: + resolution: {integrity: sha512-03spzPzMAO4pElm44m60Nj08nYonPGQXmw6Ceai/S4QK82IgwWO1EXx1s9namKzVlbVu3Jf81hb+N+8+v21/HQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1) - '@eslint-community/regexpp': 4.12.1 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.0 + '@eslint/eslintrc': 1.4.1 + '@humanwhocodes/config-array': 0.6.0 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.5 debug: 4.3.7 doctrine: 3.0.0 + enquirer: 2.4.1 escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 + eslint-scope: 6.0.0 + eslint-utils: 3.0.0(eslint@8.0.0) eslint-visitor-keys: 3.4.3 espree: 9.6.1 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 6.0.1 - find-up: 5.0.0 + functional-red-black-tree: 1.0.1 glob-parent: 6.0.2 globals: 13.24.0 - graphemer: 1.4.0 - ignore: 5.3.2 + ignore: 4.0.6 + import-fresh: 3.3.0 imurmurhash: 0.1.4 is-glob: 4.0.3 - is-path-inside: 3.0.3 js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.4.1 @@ -5995,38 +3784,88 @@ snapshots: minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.4 + progress: 2.0.3 + regexpp: 3.2.0 + semver: 7.6.3 strip-ansi: 6.0.1 + strip-json-comments: 3.1.1 text-table: 0.2.0 + v8-compile-cache: 2.4.0 transitivePeerDependencies: - supports-color + dev: true - espree@9.6.1: + /espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: acorn: 8.14.0 acorn-jsx: 5.3.2(acorn@8.14.0) eslint-visitor-keys: 3.4.3 + dev: true - esprima@4.0.1: {} + /esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + dev: true - esquery@1.6.0: + /esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} dependencies: estraverse: 5.3.0 + dev: true - esrecurse@4.3.0: + /esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} dependencies: estraverse: 5.3.0 + dev: true + + /estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + dev: true - estraverse@4.3.0: {} + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true - estraverse@5.3.0: {} + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true - esutils@2.0.3: {} + /etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} - etag@1.8.1: {} + /events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + dev: true - events@3.3.0: {} + /execa@4.1.0: + resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} + engines: {node: '>=10'} + dependencies: + cross-spawn: 7.0.5 + get-stream: 5.2.0 + human-signals: 1.1.1 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + dev: true - execa@5.1.1: + /execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} dependencies: cross-spawn: 7.0.5 get-stream: 6.0.1 @@ -6037,45 +3876,54 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 strip-final-newline: 2.0.0 + dev: true - exit@0.1.2: {} + /exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + dev: true - expect@29.7.0: + /expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/expect-utils': 29.7.0 jest-get-type: 29.6.3 jest-matcher-utils: 29.7.0 jest-message-util: 29.7.0 jest-util: 29.7.0 + dev: true - express@4.21.1: + /express@4.18.2: + resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} + engines: {node: '>= 0.10.0'} dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.3 + body-parser: 1.20.1 content-disposition: 0.5.4 content-type: 1.0.5 - cookie: 0.7.1 + cookie: 0.5.0 cookie-signature: 1.0.6 debug: 2.6.9 depd: 2.0.0 - encodeurl: 2.0.0 + encodeurl: 1.0.2 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 1.3.1 + finalhandler: 1.2.0 fresh: 0.5.2 http-errors: 2.0.0 - merge-descriptors: 1.0.3 + merge-descriptors: 1.0.1 methods: 1.1.2 on-finished: 2.4.1 parseurl: 1.3.3 - path-to-regexp: 0.1.10 + path-to-regexp: 0.1.7 proxy-addr: 2.0.7 - qs: 6.13.0 + qs: 6.11.0 range-parser: 1.2.1 safe-buffer: 5.2.1 - send: 0.19.0 - serve-static: 1.16.2 + send: 0.18.0 + serve-static: 1.15.0 setprototypeof: 1.2.0 statuses: 2.0.1 type-is: 1.6.18 @@ -6084,17 +3932,26 @@ snapshots: transitivePeerDependencies: - supports-color - external-editor@3.1.0: + /external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} dependencies: chardet: 0.7.0 iconv-lite: 0.4.24 tmp: 0.0.33 + dev: true - fast-deep-equal@3.1.3: {} + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: true - fast-diff@1.3.0: {} + /fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + dev: true - fast-glob@3.3.2: + /fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 @@ -6102,44 +3959,61 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fast-json-stable-stringify@2.1.0: {} + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true - fast-levenshtein@2.0.6: {} + /fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true - fast-safe-stringify@2.1.1: {} + /fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fast-xml-parser@4.4.1: + /fast-xml-parser@4.4.1: + resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} + hasBin: true dependencies: strnum: 1.0.5 + dev: false - fastq@1.17.1: + /fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} dependencies: reusify: 1.0.4 - fb-watchman@2.0.2: + /fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} dependencies: bser: 2.1.1 + dev: true - figures@3.2.0: + /figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} dependencies: escape-string-regexp: 1.0.5 + dev: true - file-entry-cache@6.0.1: + /file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} dependencies: flat-cache: 3.2.0 + dev: true - filelist@1.0.4: - dependencies: - minimatch: 5.1.6 - - fill-range@7.1.1: + /fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 - finalhandler@1.3.1: + /finalhandler@1.2.0: + resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + engines: {node: '>= 0.8'} dependencies: debug: 2.6.9 - encodeurl: 2.0.0 + encodeurl: 1.0.2 escape-html: 1.0.3 on-finished: 2.4.1 parseurl: 1.3.3 @@ -6148,37 +4022,51 @@ snapshots: transitivePeerDependencies: - supports-color - find-up@4.1.0: + /find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} dependencies: locate-path: 5.0.0 path-exists: 4.0.0 + dev: true - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - - flat-cache@3.2.0: + /flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} dependencies: flatted: 3.3.1 keyv: 4.5.4 rimraf: 3.0.2 + dev: true - flat@5.0.2: {} + /flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + dev: false - flatted@3.3.1: {} + /flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + dev: true - foreground-child@3.3.0: + /foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} dependencies: cross-spawn: 7.0.5 signal-exit: 4.1.0 + dev: false - fork-ts-checker-webpack-plugin@9.0.2(typescript@5.6.3)(webpack@5.96.1): + /fork-ts-checker-webpack-plugin@8.0.0(typescript@5.1.3)(webpack@5.87.0): + resolution: {integrity: sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==} + engines: {node: '>=12.13.0', yarn: '>=1.0.0'} + peerDependencies: + typescript: '>3.6.0' + webpack: ^5.11.0 dependencies: '@babel/code-frame': 7.26.2 chalk: 4.1.2 - chokidar: 3.6.0 - cosmiconfig: 8.3.6(typescript@5.6.3) + chokidar: 3.5.3 + cosmiconfig: 7.1.0 deepmerge: 4.3.1 fs-extra: 10.1.0 memfs: 3.5.3 @@ -6187,49 +4075,85 @@ snapshots: schema-utils: 3.3.0 semver: 7.6.3 tapable: 2.2.1 - typescript: 5.6.3 - webpack: 5.96.1 + typescript: 5.1.3 + webpack: 5.87.0 + dev: true - form-data@4.0.1: + /form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + engines: {node: '>= 6'} dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 + dev: true - formidable@3.5.2: + /formidable@3.5.2: + resolution: {integrity: sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==} dependencies: dezalgo: 1.0.4 hexoid: 2.0.0 once: 1.4.0 + dev: true - forwarded@0.2.0: {} + /forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} - fresh@0.5.2: {} + /fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} - fs-extra@10.1.0: + /fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} dependencies: graceful-fs: 4.2.11 jsonfile: 6.1.0 universalify: 2.0.1 + dev: true - fs-monkey@1.0.6: {} + /fs-monkey@1.0.6: + resolution: {integrity: sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==} + dev: true - fs.realpath@1.0.0: {} + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true - fsevents@2.3.3: + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true optional: true - function-bind@1.1.2: {} + /function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + /functional-red-black-tree@1.0.1: + resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} + dev: true - generate-function@2.3.1: + /generate-function@2.3.1: + resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} dependencies: is-property: 1.0.2 + dev: false - gensync@1.0.0-beta.2: {} + /gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + dev: true - get-caller-file@2.0.5: {} + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} - get-intrinsic@1.2.4: + /get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 function-bind: 1.1.2 @@ -6237,21 +4161,43 @@ snapshots: has-symbols: 1.0.3 hasown: 2.0.2 - get-package-type@0.1.0: {} + /get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + dev: true + + /get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + dependencies: + pump: 3.0.2 + dev: true - get-stream@6.0.1: {} + /get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + dev: true - glob-parent@5.1.2: + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 - glob-parent@6.0.2: + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} dependencies: is-glob: 4.0.3 + dev: true - glob-to-regexp@0.4.1: {} + /glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + dev: true - glob@10.4.2: + /glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true dependencies: foreground-child: 3.3.0 jackspeak: 3.4.3 @@ -6259,8 +4205,11 @@ snapshots: minipass: 7.1.2 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + dev: false - glob@7.2.3: + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -6268,44 +4217,99 @@ snapshots: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 + dev: true + + /glob@9.3.5: + resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + fs.realpath: 1.0.0 + minimatch: 8.0.4 + minipass: 4.2.8 + path-scurry: 1.11.1 + dev: true - globals@11.12.0: {} + /globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + dev: true - globals@13.24.0: + /globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} dependencies: type-fest: 0.20.2 + dev: true + + /globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + dev: true - gopd@1.0.1: + /gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: get-intrinsic: 1.2.4 - graceful-fs@4.2.11: {} + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + dev: true - graphemer@1.4.0: {} + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + dev: true - has-flag@4.0.0: {} + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} - has-own-prop@2.0.0: {} + /has-own-prop@2.0.0: + resolution: {integrity: sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==} + engines: {node: '>=8'} + dev: true - has-property-descriptors@1.0.2: + /has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} dependencies: es-define-property: 1.0.0 - has-proto@1.0.3: {} + /has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} - has-symbols@1.0.3: {} + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} - hasown@2.0.2: + /hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 - hexoid@2.0.0: {} + /hexoid@2.0.0: + resolution: {integrity: sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==} + engines: {node: '>=8'} + dev: true - highlight.js@10.7.3: {} + /highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + dev: false - html-escaper@2.0.2: {} + /html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + dev: true - http-errors@2.0.0: + /http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} dependencies: depd: 2.0.0 inherits: 2.0.4 @@ -6313,40 +4317,78 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 - human-signals@2.1.0: {} + /human-signals@1.1.1: + resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} + engines: {node: '>=8.12.0'} + dev: true - iconv-lite@0.4.24: + /human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + dev: true + + /iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} dependencies: safer-buffer: 2.1.2 - iconv-lite@0.6.3: + /iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} dependencies: safer-buffer: 2.1.2 + dev: false + + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ieee754@1.2.1: {} + /ignore@4.0.6: + resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} + engines: {node: '>= 4'} + dev: true - ignore@5.3.2: {} + /ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + dev: true - import-fresh@3.3.0: + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 + dev: true - import-local@3.2.0: + /import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true dependencies: pkg-dir: 4.2.0 resolve-cwd: 3.0.0 + dev: true - imurmurhash@0.1.4: {} + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: true - inflight@1.0.6: + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. dependencies: once: 1.4.0 wrappy: 1.0.2 + dev: true - inherits@2.0.4: {} + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - inquirer@8.2.6: + /inquirer@8.2.4: + resolution: {integrity: sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==} + engines: {node: '>=12.0.0'} dependencies: ansi-escapes: 4.3.2 chalk: 4.1.2 @@ -6362,67 +4404,113 @@ snapshots: string-width: 4.2.3 strip-ansi: 6.0.1 through: 2.3.8 - wrap-ansi: 6.2.0 + wrap-ansi: 7.0.0 + dev: true - inquirer@9.2.15: + /inquirer@8.2.5: + resolution: {integrity: sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==} + engines: {node: '>=12.0.0'} dependencies: - '@ljharb/through': 2.3.13 ansi-escapes: 4.3.2 - chalk: 5.3.0 + chalk: 4.1.2 cli-cursor: 3.1.0 - cli-width: 4.1.0 + cli-width: 3.0.0 external-editor: 3.1.0 figures: 3.2.0 lodash: 4.17.21 - mute-stream: 1.0.0 + mute-stream: 0.0.8 ora: 5.4.1 - run-async: 3.0.0 + run-async: 2.4.1 rxjs: 7.8.1 string-width: 4.2.3 strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 + through: 2.3.8 + wrap-ansi: 7.0.0 + dev: true + + /interpret@1.4.0: + resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} + engines: {node: '>= 0.10'} + dev: true - ipaddr.js@1.9.1: {} + /ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} - is-arrayish@0.2.1: {} + /is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + dev: true - is-binary-path@2.1.0: + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} dependencies: binary-extensions: 2.3.0 + dev: true - is-core-module@2.15.1: + /is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + engines: {node: '>= 0.4'} dependencies: hasown: 2.0.2 + dev: true - is-extglob@2.1.1: {} + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} - is-fullwidth-code-point@3.0.0: {} + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} - is-generator-fn@2.1.0: {} + /is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + dev: true - is-glob@4.0.3: + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 - is-interactive@1.0.0: {} - - is-number@7.0.0: {} + /is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + dev: true - is-path-inside@3.0.3: {} + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} - is-property@1.0.2: {} + /is-property@1.0.2: + resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} + dev: false - is-stream@2.0.1: {} + /is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: true - is-unicode-supported@0.1.0: {} + /is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + dev: true - isarray@1.0.0: {} + /isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - isexe@2.0.0: {} + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - istanbul-lib-coverage@3.2.2: {} + /istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + dev: true - istanbul-lib-instrument@5.2.1: + /istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} dependencies: '@babel/core': 7.26.0 '@babel/parser': 7.26.2 @@ -6431,8 +4519,11 @@ snapshots: semver: 6.3.1 transitivePeerDependencies: - supports-color + dev: true - istanbul-lib-instrument@6.0.3: + /istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} dependencies: '@babel/core': 7.26.0 '@babel/parser': 7.26.2 @@ -6441,54 +4532,66 @@ snapshots: semver: 7.6.3 transitivePeerDependencies: - supports-color + dev: true - istanbul-lib-report@3.0.1: + /istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} dependencies: istanbul-lib-coverage: 3.2.2 make-dir: 4.0.0 supports-color: 7.2.0 + dev: true - istanbul-lib-source-maps@4.0.1: + /istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} dependencies: debug: 4.3.7 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: - supports-color + dev: true - istanbul-reports@3.1.7: + /istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} dependencies: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 + dev: true - iterare@1.2.1: {} + /iterare@1.2.1: + resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} + engines: {node: '>=6'} - jackspeak@3.4.3: + /jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 + dev: false - jake@10.9.2: - dependencies: - async: 3.2.6 - chalk: 4.1.2 - filelist: 1.0.4 - minimatch: 3.1.2 - - jest-changed-files@29.7.0: + /jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: execa: 5.1.1 jest-util: 29.7.0 p-limit: 3.1.0 + dev: true - jest-circus@29.7.0: + /jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/environment': 29.7.0 '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.6 + '@types/node': 20.3.1 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.3 @@ -6507,17 +4610,26 @@ snapshots: transitivePeerDependencies: - babel-plugin-macros - supports-color + dev: true - jest-cli@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): + /jest-cli@29.7.0(@types/node@20.3.1)(ts-node@10.9.1): + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + '@jest/core': 29.7.0(ts-node@10.9.1) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + create-jest: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -6526,12 +4638,24 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + dev: true - jest-config@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): + /jest-config@29.7.0(@types/node@20.3.1)(ts-node@10.9.1): + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true dependencies: '@babel/core': 7.26.0 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 + '@types/node': 20.3.1 babel-jest: 29.7.0(@babel/core@7.26.0) chalk: 4.1.2 ci-info: 3.9.0 @@ -6551,48 +4675,64 @@ snapshots: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 20.17.6 - ts-node: 10.9.2(@types/node@20.17.6)(typescript@5.6.3) + ts-node: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) transitivePeerDependencies: - babel-plugin-macros - supports-color + dev: true - jest-diff@29.7.0: + /jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: chalk: 4.1.2 diff-sequences: 29.6.3 jest-get-type: 29.6.3 pretty-format: 29.7.0 + dev: true - jest-docblock@29.7.0: + /jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: detect-newline: 3.1.0 + dev: true - jest-each@29.7.0: + /jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 jest-get-type: 29.6.3 jest-util: 29.7.0 pretty-format: 29.7.0 + dev: true - jest-environment-node@29.7.0: + /jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.6 + '@types/node': 20.3.1 jest-mock: 29.7.0 jest-util: 29.7.0 + dev: true - jest-get-type@29.6.3: {} + /jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true - jest-haste-map@29.7.0: + /jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.17.6 + '@types/node': 20.3.1 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -6603,20 +4743,29 @@ snapshots: walker: 1.0.8 optionalDependencies: fsevents: 2.3.3 + dev: true - jest-leak-detector@29.7.0: + /jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: jest-get-type: 29.6.3 pretty-format: 29.7.0 + dev: true - jest-matcher-utils@29.7.0: + /jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: chalk: 4.1.2 jest-diff: 29.7.0 jest-get-type: 29.6.3 pretty-format: 29.7.0 + dev: true - jest-message-util@29.7.0: + /jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/code-frame': 7.26.2 '@jest/types': 29.6.3 @@ -6627,27 +4776,47 @@ snapshots: pretty-format: 29.7.0 slash: 3.0.0 stack-utils: 2.0.6 + dev: true - jest-mock@29.7.0: + /jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.17.6 + '@types/node': 20.3.1 jest-util: 29.7.0 + dev: true - jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): - optionalDependencies: + /jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + dependencies: jest-resolve: 29.7.0 + dev: true - jest-regex-util@29.6.3: {} + /jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true - jest-resolve-dependencies@29.7.0: + /jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: jest-regex-util: 29.6.3 jest-snapshot: 29.7.0 transitivePeerDependencies: - supports-color + dev: true - jest-resolve@29.7.0: + /jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: chalk: 4.1.2 graceful-fs: 4.2.11 @@ -6658,15 +4827,18 @@ snapshots: resolve: 1.22.8 resolve.exports: 2.0.2 slash: 3.0.0 + dev: true - jest-runner@29.7.0: + /jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/console': 29.7.0 '@jest/environment': 29.7.0 '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.6 + '@types/node': 20.3.1 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -6684,8 +4856,11 @@ snapshots: source-map-support: 0.5.13 transitivePeerDependencies: - supports-color + dev: true - jest-runtime@29.7.0: + /jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 @@ -6694,7 +4869,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.6 + '@types/node': 20.3.1 chalk: 4.1.2 cjs-module-lexer: 1.4.1 collect-v8-coverage: 1.0.2 @@ -6711,8 +4886,11 @@ snapshots: strip-bom: 4.0.0 transitivePeerDependencies: - supports-color + dev: true - jest-snapshot@29.7.0: + /jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/core': 7.26.0 '@babel/generator': 7.26.2 @@ -6736,17 +4914,23 @@ snapshots: semver: 7.6.3 transitivePeerDependencies: - supports-color + dev: true - jest-util@29.7.0: + /jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 - '@types/node': 20.17.6 + '@types/node': 20.3.1 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 picomatch: 2.3.1 + dev: true - jest-validate@29.7.0: + /jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 camelcase: 6.3.0 @@ -6754,89 +4938,142 @@ snapshots: jest-get-type: 29.6.3 leven: 3.1.0 pretty-format: 29.7.0 + dev: true - jest-watcher@29.7.0: + /jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.6 + '@types/node': 20.3.1 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 jest-util: 29.7.0 string-length: 4.0.2 + dev: true - jest-worker@27.5.1: + /jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 20.17.6 + '@types/node': 20.3.1 merge-stream: 2.0.0 supports-color: 8.1.1 + dev: true - jest-worker@29.7.0: + /jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 20.17.6 + '@types/node': 20.3.1 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 + dev: true - jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): + /jest@29.5.0(@types/node@20.3.1)(ts-node@10.9.1): + resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true dependencies: - '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + '@jest/core': 29.7.0(ts-node@10.9.1) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + jest-cli: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - ts-node + dev: true - jiti@2.4.0: {} + /jiti@2.4.0: + resolution: {integrity: sha512-H5UpaUI+aHOqZXlYOaFP/8AzKsg+guWu+Pr3Y8i7+Y3zr1aXAvCvTAQ1RxSc6oVD8R8c7brgNtTVP91E7upH/g==} + hasBin: true + dev: false - joi@17.13.3: + /joi@17.13.3: + resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} dependencies: '@hapi/hoek': 9.3.0 '@hapi/topo': 5.1.0 '@sideway/address': 4.1.5 '@sideway/formula': 3.0.1 '@sideway/pinpoint': 2.0.0 + dev: false - js-tokens@4.0.0: {} + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: true - js-yaml@3.14.1: + /js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true dependencies: argparse: 1.0.10 esprima: 4.0.1 + dev: true - js-yaml@4.1.0: + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true dependencies: argparse: 2.0.1 - jsesc@3.0.2: {} - - json-buffer@3.0.1: {} + /jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + dev: true - json-parse-even-better-errors@2.3.1: {} + /json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + dev: true - json-schema-traverse@0.4.1: {} + /json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + dev: true - json-schema-traverse@1.0.0: {} + /json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true - json-stable-stringify-without-jsonify@1.0.1: {} + /json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + dev: true - json5@2.2.3: {} + /json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true - jsonc-parser@3.2.1: {} + /json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + dev: true - jsonc-parser@3.3.1: {} + /jsonc-parser@3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: true - jsonfile@6.1.0: + /jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} dependencies: universalify: 2.0.1 optionalDependencies: graceful-fs: 4.2.11 + dev: true - jsonwebtoken@9.0.2: + /jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} dependencies: jws: 3.2.2 lodash.includes: 4.3.0 @@ -6848,46 +5085,68 @@ snapshots: lodash.once: 4.1.1 ms: 2.1.3 semver: 7.6.3 + dev: false - jwa@1.4.1: + /jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} dependencies: buffer-equal-constant-time: 1.0.1 ecdsa-sig-formatter: 1.0.11 safe-buffer: 5.2.1 + dev: false - jws@3.2.2: + /jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} dependencies: jwa: 1.4.1 safe-buffer: 5.2.1 + dev: false - keyv@4.5.4: + /keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} dependencies: json-buffer: 3.0.1 + dev: true - kleur@3.0.3: {} + /kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + dev: true - leven@3.1.0: {} + /leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + dev: true - levn@0.4.1: + /levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 + dev: true - libphonenumber-js@1.11.13: {} + /libphonenumber-js@1.11.14: + resolution: {integrity: sha512-sexvAfwcW1Lqws4zFp8heAtAEXbEDnvkYCEGzvOoMgZR7JhXo/IkE9MkkGACgBed5fWqh3ShBGnJBdDnU9N8EQ==} - lines-and-columns@1.2.4: {} + /lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: true - loader-runner@4.3.0: {} + /loader-runner@4.3.0: + resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} + engines: {node: '>=6.11.5'} + dev: true - locate-path@5.0.0: + /locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} dependencies: p-locate: 4.1.0 + dev: true - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - - locter@2.1.5: + /locter@2.1.5: + resolution: {integrity: sha512-eI57PuVxigQ0GBscGIIFGPB467E5zKODHD3XGuknzLvf7HdnvRw3GdZVGj1J8XKsKOYovZQesX/oOdTwbdjwuQ==} dependencies: destr: 2.0.3 ebec: 2.3.0 @@ -6895,120 +5154,223 @@ snapshots: flat: 5.0.2 jiti: 2.4.0 yaml: 2.6.0 + dev: false - lodash.includes@4.3.0: {} + /lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + dev: false - lodash.isboolean@3.0.3: {} + /lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + dev: false - lodash.isinteger@4.0.4: {} + /lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + dev: false - lodash.isnumber@3.0.3: {} + /lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + dev: false - lodash.isplainobject@4.0.6: {} + /lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + dev: false - lodash.isstring@4.0.1: {} + /lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + dev: false - lodash.memoize@4.1.2: {} + /lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + dev: true - lodash.merge@4.6.2: {} + /lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true - lodash.once@4.1.1: {} + /lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + dev: false - lodash@4.17.21: {} + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - log-symbols@4.1.0: + /log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} dependencies: chalk: 4.1.2 is-unicode-supported: 0.1.0 + dev: true - long@5.2.3: {} + /long@5.2.3: + resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} + dev: false - lower-case@2.0.2: + /lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: tslib: 2.8.1 + dev: false - lru-cache@10.4.3: {} + /lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@5.1.1: + /lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: yallist: 3.1.1 + dev: true - lru-cache@7.18.3: {} + /lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + dev: false + + /lru.min@1.1.1: + resolution: {integrity: sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==} + engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} + dev: false - lru.min@1.1.1: {} + /macos-release@2.5.1: + resolution: {integrity: sha512-DXqXhEM7gW59OjZO8NIjBCz9AQ1BEMrfiOAl4AYByHCtVHRF4KoGNO8mqQeM8lRCtQe/UnJ4imO/d2HdkKsd+A==} + engines: {node: '>=6'} + dev: true - magic-string@0.30.8: + /magic-string@0.30.0: + resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==} + engines: {node: '>=12'} dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + dev: true - make-dir@4.0.0: + /make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} dependencies: semver: 7.6.3 + dev: true - make-error@1.3.6: {} + /make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - makeerror@1.0.12: + /makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} dependencies: tmpl: 1.0.5 + dev: true - media-typer@0.3.0: {} + /media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} - memfs@3.5.3: + /memfs@3.5.3: + resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} + engines: {node: '>= 4.0.0'} dependencies: fs-monkey: 1.0.6 + dev: true - merge-descriptors@1.0.3: {} + /merge-descriptors@1.0.1: + resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} - merge-stream@2.0.0: {} + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true - merge2@1.4.1: {} + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} - methods@1.1.2: {} + /methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} - micromatch@4.0.8: + /micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} dependencies: braces: 3.0.3 picomatch: 2.3.1 - mime-db@1.52.0: {} + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} - mime-types@2.1.35: + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 - mime@1.6.0: {} + /mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true - mime@2.6.0: {} + /mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + dev: true - mimic-fn@2.1.0: {} + /mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + dev: true - minimatch@3.1.2: + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.11 + dev: true - minimatch@5.1.6: + /minimatch@8.0.4: + resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==} + engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 + dev: true - minimatch@9.0.5: + /minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 - minimist@1.2.8: {} + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + /minipass@4.2.8: + resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} + engines: {node: '>=8'} + dev: true - minipass@7.1.2: {} + /minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} - mkdirp@0.5.6: + /mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true dependencies: minimist: 1.2.8 - mkdirp@2.1.6: {} + /mkdirp@2.1.6: + resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} + engines: {node: '>=10'} + hasBin: true + dev: false - ms@2.0.0: {} + /ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - ms@2.1.3: {} + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - multer@1.4.4-lts.1: + /multer@1.4.4-lts.1: + resolution: {integrity: sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==} + engines: {node: '>= 6.0.0'} dependencies: append-field: 1.0.0 busboy: 1.6.0 @@ -7018,11 +5380,13 @@ snapshots: type-is: 1.6.18 xtend: 4.0.2 - mute-stream@0.0.8: {} - - mute-stream@1.0.0: {} + /mute-stream@0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + dev: true - mysql2@3.11.4: + /mysql2@3.11.4: + resolution: {integrity: sha512-Z2o3tY4Z8EvSRDwknaC40MdZ3+m0sKbpnXrShQLdxPrAvcNli7jLrD2Zd2IzsRMw4eK9Yle500FDmlkIqp+krg==} + engines: {node: '>= 8.0'} dependencies: aws-ssl-profiles: 1.1.2 denque: 2.1.0 @@ -7033,69 +5397,123 @@ snapshots: named-placeholders: 1.1.3 seq-queue: 0.0.5 sqlstring: 2.3.3 + dev: false - mz@2.7.0: + /mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} dependencies: any-promise: 1.3.0 object-assign: 4.1.1 thenify-all: 1.6.0 + dev: false - named-placeholders@1.1.3: + /named-placeholders@1.1.3: + resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} + engines: {node: '>=12.0.0'} dependencies: lru-cache: 7.18.3 + dev: false - natural-compare@1.4.0: {} + /natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true - negotiator@0.6.3: {} + /negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} - neo-async@2.6.2: {} + /neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + dev: true - no-case@3.0.4: + /no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: lower-case: 2.0.2 tslib: 2.8.1 + dev: false - node-abort-controller@3.1.1: {} + /node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + dev: true - node-addon-api@8.2.2: {} + /node-addon-api@8.2.2: + resolution: {integrity: sha512-9emqXAKhVoNrQ792nLI/wpzPpJ/bj/YXxW0CvAau1+RdGBcCRF1Dmz7719zgVsQNrzHl9Tzn3ImZ4qWFarWL0A==} + engines: {node: ^18 || ^20 || >= 21} + dev: false - node-emoji@1.11.0: + /node-emoji@1.11.0: + resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} dependencies: lodash: 4.17.21 + dev: true - node-fetch@2.7.0: + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true dependencies: whatwg-url: 5.0.0 - node-gyp-build@4.8.2: {} + /node-gyp-build@4.8.2: + resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==} + hasBin: true + dev: false - node-int64@0.4.0: {} + /node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + dev: true - node-releases@2.0.18: {} + /node-releases@2.0.18: + resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + dev: true - normalize-path@3.0.0: {} + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true - npm-run-path@4.0.1: + /npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} dependencies: path-key: 3.1.1 + dev: true - object-assign@4.1.1: {} + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} - object-inspect@1.13.2: {} + /object-inspect@1.13.3: + resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==} + engines: {node: '>= 0.4'} - on-finished@2.4.1: + /on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} dependencies: ee-first: 1.1.1 - once@1.4.0: + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: wrappy: 1.0.2 + dev: true - onetime@5.1.2: + /onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} dependencies: mimic-fn: 2.1.0 + dev: true - optionator@0.9.4: + /optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} dependencies: deep-is: 0.1.4 fast-levenshtein: 2.0.6 @@ -7103,8 +5521,11 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 word-wrap: 1.2.5 + dev: true - ora@5.4.1: + /ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} dependencies: bl: 4.1.0 chalk: 4.1.2 @@ -7115,96 +5536,179 @@ snapshots: log-symbols: 4.1.0 strip-ansi: 6.0.1 wcwidth: 1.0.1 + dev: true + + /os-name@4.0.1: + resolution: {integrity: sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw==} + engines: {node: '>=10'} + dependencies: + macos-release: 2.5.1 + windows-release: 4.0.0 + dev: true - os-tmpdir@1.0.2: {} + /os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + dev: true - p-limit@2.3.0: + /p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} dependencies: p-try: 2.2.0 + dev: true - p-limit@3.1.0: + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} dependencies: yocto-queue: 0.1.0 + dev: true - p-locate@4.1.0: + /p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} dependencies: p-limit: 2.3.0 + dev: true - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - - p-try@2.2.0: {} + /p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + dev: true - package-json-from-dist@1.0.1: {} + /package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + dev: false - parent-module@1.0.1: + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} dependencies: callsites: 3.1.0 + dev: true - parse-json@5.2.0: + /parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} dependencies: '@babel/code-frame': 7.26.2 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + dev: true - parse5-htmlparser2-tree-adapter@6.0.1: + /parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} dependencies: parse5: 6.0.1 + dev: false - parse5@5.1.1: {} + /parse5@5.1.1: + resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} + dev: false - parse5@6.0.1: {} + /parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + dev: false - parseurl@1.3.3: {} + /parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} - pascal-case@3.1.2: + /pascal-case@3.1.2: + resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} dependencies: no-case: 3.0.4 tslib: 2.8.1 + dev: false - path-exists@4.0.0: {} + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true - path-is-absolute@1.0.1: {} + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true - path-key@3.1.1: {} + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} - path-parse@1.0.7: {} + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true - path-scurry@1.11.1: + /path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} dependencies: lru-cache: 10.4.3 minipass: 7.1.2 - path-to-regexp@0.1.10: {} + /path-to-regexp@0.1.7: + resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + + /path-to-regexp@3.2.0: + resolution: {integrity: sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==} - path-to-regexp@3.3.0: {} + /path-to-regexp@3.3.0: + resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} + dev: false - path-type@4.0.0: {} + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true - pg-cloudflare@1.1.1: + /pg-cloudflare@1.1.1: + resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} + requiresBuild: true + dev: false optional: true - pg-connection-string@2.7.0: {} + /pg-connection-string@2.7.0: + resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} + dev: false - pg-int8@1.0.1: {} + /pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + dev: false - pg-pool@3.7.0(pg@8.13.1): + /pg-pool@3.7.0(pg@8.13.1): + resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==} + peerDependencies: + pg: '>=8.0' dependencies: pg: 8.13.1 + dev: false - pg-protocol@1.7.0: {} + /pg-protocol@1.7.0: + resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} + dev: false - pg-types@2.2.0: + /pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} dependencies: pg-int8: 1.0.1 postgres-array: 2.0.0 postgres-bytea: 1.0.0 postgres-date: 1.0.7 postgres-interval: 1.2.0 + dev: false - pg@8.13.1: + /pg@8.13.1: + resolution: {integrity: sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true dependencies: pg-connection-string: 2.7.0 pg-pool: 3.7.0(pg@8.13.1) @@ -7213,92 +5717,184 @@ snapshots: pgpass: 1.0.5 optionalDependencies: pg-cloudflare: 1.1.1 + dev: false - pgpass@1.0.5: + /pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} dependencies: split2: 4.2.0 + dev: false - picocolors@1.1.1: {} - - picomatch@2.3.1: {} + /picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + dev: true - picomatch@4.0.1: {} + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} - pirates@4.0.6: {} + /pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + dev: true - pkg-dir@4.2.0: + /pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} dependencies: find-up: 4.1.0 + dev: true - pluralize@8.0.0: {} + /pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + dev: true - postgres-array@2.0.0: {} + /postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + dev: false - postgres-bytea@1.0.0: {} + /postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + dev: false - postgres-date@1.0.7: {} + /postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + dev: false - postgres-interval@1.2.0: + /postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} dependencies: xtend: 4.0.2 + dev: false - prelude-ls@1.2.1: {} + /prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true - prettier-linter-helpers@1.0.0: + /prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} dependencies: fast-diff: 1.3.0 + dev: true - prettier@3.3.3: {} + /prettier@3.0.0: + resolution: {integrity: sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==} + engines: {node: '>=14'} + hasBin: true + dev: true - pretty-format@29.7.0: + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 react-is: 18.3.1 + dev: true + + /process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - process-nextick-args@2.0.1: {} + /progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + dev: true - prompts@2.4.2: + /prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} dependencies: kleur: 3.0.3 sisteransi: 1.0.5 + dev: true - proxy-addr@2.0.7: + /proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} dependencies: forwarded: 0.2.0 ipaddr.js: 1.9.1 - punycode@2.3.1: {} + /pump@3.0.2: + resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + dev: true + + /punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + dev: true + + /pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + dev: true - pure-rand@6.1.0: {} + /qs@6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.6 - qs@6.13.0: + /qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} dependencies: side-channel: 1.0.6 + dev: true - queue-microtask@1.2.3: {} + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - randombytes@2.1.0: + /randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} dependencies: safe-buffer: 5.2.1 + dev: true - range-parser@1.2.1: {} + /range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} - rapiq@0.9.0: + /rapiq@0.9.0: + resolution: {integrity: sha512-k4oT4RarFBrlLMJ49xUTeQpa/us0uU4I70D/UEnK3FWQ4GENzei01rEQAmvPKAIzACo4NMW+YcYJ7EVfSa7EFg==} dependencies: ebec: 1.1.1 smob: 1.5.0 + dev: false + + /raw-body@2.5.1: + resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 - raw-body@2.5.2: + /raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} dependencies: bytes: 3.1.2 http-errors: 2.0.0 iconv-lite: 0.4.24 unpipe: 1.0.0 - react-is@18.3.1: {} + /react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + dev: true - readable-stream@2.3.8: + /readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} dependencies: core-util-is: 1.0.3 inherits: 2.0.4 @@ -7308,80 +5904,161 @@ snapshots: string_decoder: 1.1.1 util-deprecate: 1.0.2 - readable-stream@3.6.2: + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} dependencies: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 + dev: true - readdirp@3.6.0: + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 + dev: true + + /rechoir@0.6.2: + resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} + engines: {node: '>= 0.10'} + dependencies: + resolve: 1.22.8 + dev: true + + /reflect-metadata@0.2.0: + resolution: {integrity: sha512-vUN0wuk3MuhSVMfU/ImnPQAK8QZcXJ339DtVsP3jDscxCe6dT+PsOe3J1BYS9Ec2Fd4oC6ry6bCBebzTya0IYw==} + deprecated: This version has a critical bug in fallback handling. Please upgrade to reflect-metadata@0.2.2 or newer. + + /reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + dev: false - reflect-metadata@0.2.2: {} + /regexpp@3.2.0: + resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} + engines: {node: '>=8'} + dev: true - repeat-string@1.6.1: {} + /repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + dev: true - require-directory@2.1.1: {} + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} - require-from-string@2.0.2: {} + /require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + dev: true - resolve-cwd@3.0.0: + /resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} dependencies: resolve-from: 5.0.0 + dev: true - resolve-from@4.0.0: {} + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true - resolve-from@5.0.0: {} + /resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + dev: true - resolve.exports@2.0.2: {} + /resolve.exports@2.0.2: + resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} + engines: {node: '>=10'} + dev: true - resolve@1.22.8: + /resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true dependencies: is-core-module: 2.15.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + dev: true - restore-cursor@3.1.0: + /restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} dependencies: onetime: 5.1.2 signal-exit: 3.0.7 + dev: true - reusify@1.0.4: {} + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@3.0.2: + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true dependencies: glob: 7.2.3 + dev: true - run-async@2.4.1: {} + /rimraf@4.4.1: + resolution: {integrity: sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==} + engines: {node: '>=14'} + hasBin: true + dependencies: + glob: 9.3.5 + dev: true - run-async@3.0.0: {} + /run-async@2.4.1: + resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} + engines: {node: '>=0.12.0'} + dev: true - run-parallel@1.2.0: + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 - rxjs@7.8.1: + /rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: tslib: 2.8.1 - safe-buffer@5.1.2: {} + /safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - safe-buffer@5.2.1: {} + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safer-buffer@2.1.2: {} + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - schema-utils@3.3.0: + /schema-utils@3.3.0: + resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} + engines: {node: '>= 10.13.0'} dependencies: '@types/json-schema': 7.0.15 ajv: 6.12.6 ajv-keywords: 3.5.2(ajv@6.12.6) + dev: true - semver@6.3.1: {} + /semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + dev: true - semver@7.6.3: {} + /semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true - send@0.19.0: + /send@0.18.0: + resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + engines: {node: '>= 0.8.0'} dependencies: debug: 2.6.9 depd: 2.0.0 @@ -7399,22 +6076,30 @@ snapshots: transitivePeerDependencies: - supports-color - seq-queue@0.0.5: {} + /seq-queue@0.0.5: + resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} + dev: false - serialize-javascript@6.0.2: + /serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} dependencies: randombytes: 2.1.0 + dev: true - serve-static@1.16.2: + /serve-static@1.15.0: + resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} + engines: {node: '>= 0.8.0'} dependencies: - encodeurl: 2.0.0 + encodeurl: 1.0.2 escape-html: 1.0.3 parseurl: 1.3.3 - send: 0.19.0 + send: 0.18.0 transitivePeerDependencies: - supports-color - set-function-length@1.2.2: + /set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} dependencies: define-data-property: 1.1.4 es-errors: 1.3.0 @@ -7423,110 +6108,201 @@ snapshots: gopd: 1.0.1 has-property-descriptors: 1.0.2 - setprototypeof@1.2.0: {} + /setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - sha.js@2.4.11: + /sha.js@2.4.11: + resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} + hasBin: true dependencies: inherits: 2.0.4 safe-buffer: 5.2.1 + dev: false - shebang-command@2.0.0: + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 - shebang-regex@3.0.0: {} + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + /shelljs@0.8.5: + resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} + engines: {node: '>=4'} + hasBin: true + dependencies: + glob: 7.2.3 + interpret: 1.4.0 + rechoir: 0.6.2 + dev: true - side-channel@1.0.6: + /side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 es-errors: 1.3.0 get-intrinsic: 1.2.4 - object-inspect: 1.13.2 + object-inspect: 1.13.3 - signal-exit@3.0.7: {} + /signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: true - signal-exit@4.1.0: {} + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: false - sisteransi@1.0.5: {} + /sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + dev: true - slash@3.0.0: {} + /slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + dev: true - smob@1.5.0: {} + /smob@1.5.0: + resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} + dev: false - source-map-support@0.5.13: + /source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} dependencies: buffer-from: 1.1.2 source-map: 0.6.1 + dev: true - source-map-support@0.5.21: + /source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} dependencies: buffer-from: 1.1.2 source-map: 0.6.1 + dev: true - source-map@0.6.1: {} + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: true - source-map@0.7.4: {} + /source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + dev: true - split2@4.2.0: {} + /split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + dev: false - sprintf-js@1.0.3: {} + /sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + dev: true - sqlstring@2.3.3: {} + /sqlstring@2.3.3: + resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} + engines: {node: '>= 0.6'} + dev: false - stack-utils@2.0.6: + /stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} dependencies: escape-string-regexp: 2.0.0 + dev: true - statuses@2.0.1: {} + /statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} - std-env@3.7.0: {} + /std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + dev: false - streamsearch@1.1.0: {} + /streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} - string-length@4.0.2: + /string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} dependencies: char-regex: 1.0.2 strip-ansi: 6.0.1 + dev: true - string-width@4.2.3: + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string-width@5.1.2: + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 + dev: false - string_decoder@1.1.1: + /string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} dependencies: safe-buffer: 5.1.2 - string_decoder@1.3.0: + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: safe-buffer: 5.2.1 + dev: true - strip-ansi@6.0.1: + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} dependencies: ansi-regex: 6.1.0 + dev: false - strip-bom@3.0.0: {} + /strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + dev: true - strip-bom@4.0.0: {} + /strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + dev: true - strip-final-newline@2.0.0: {} + /strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + dev: true - strip-json-comments@3.1.1: {} + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true - strnum@1.0.5: {} + /strnum@1.0.5: + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + dev: false - superagent@9.0.2: + /superagent@9.0.2: + resolution: {integrity: sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==} + engines: {node: '>=14.18.0'} dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 @@ -7539,38 +6315,99 @@ snapshots: qs: 6.13.0 transitivePeerDependencies: - supports-color + dev: true - supertest@7.0.0: + /supertest@7.0.0: + resolution: {integrity: sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==} + engines: {node: '>=14.18.0'} dependencies: methods: 1.1.2 superagent: 9.0.2 transitivePeerDependencies: - supports-color + dev: true - supports-color@7.2.0: + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} dependencies: has-flag: 4.0.0 - supports-color@8.1.1: + /supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} dependencies: has-flag: 4.0.0 + dev: true - supports-preserve-symlinks-flag@1.0.0: {} + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true - swagger-ui-dist@5.18.2: + /swagger-ui-dist@5.18.2: + resolution: {integrity: sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw==} dependencies: '@scarf/scarf': 1.4.0 + dev: false - symbol-observable@4.0.0: {} + /symbol-observable@4.0.0: + resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} + engines: {node: '>=0.10'} + dev: true - synckit@0.9.2: + /synckit@0.8.8: + resolution: {integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==} + engines: {node: ^14.18.0 || >=16.0.0} dependencies: '@pkgr/core': 0.1.1 tslib: 2.8.1 + dev: true + + /tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + dev: true - tapable@2.2.1: {} + /terser-webpack-plugin@5.3.10(webpack@5.87.0): + resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + jest-worker: 27.5.1 + schema-utils: 3.3.0 + serialize-javascript: 6.0.2 + terser: 5.36.0 + webpack: 5.87.0 + dev: true - terser-webpack-plugin@5.3.10(webpack@5.96.1): + /terser-webpack-plugin@5.3.10(webpack@5.96.1): + resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 @@ -7578,133 +6415,228 @@ snapshots: serialize-javascript: 6.0.2 terser: 5.36.0 webpack: 5.96.1 + dev: true - terser@5.36.0: + /terser@5.36.0: + resolution: {integrity: sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==} + engines: {node: '>=10'} + hasBin: true dependencies: '@jridgewell/source-map': 0.3.6 acorn: 8.14.0 commander: 2.20.3 source-map-support: 0.5.21 + dev: true - test-exclude@6.0.0: + /test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} dependencies: '@istanbuljs/schema': 0.1.3 glob: 7.2.3 minimatch: 3.1.2 + dev: true - text-table@0.2.0: {} + /text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true - thenify-all@1.6.0: + /thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} dependencies: thenify: 3.3.1 + dev: false - thenify@3.3.1: + /thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} dependencies: any-promise: 1.3.0 + dev: false - through@2.3.8: {} + /through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + dev: true - tmp@0.0.33: + /tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} dependencies: os-tmpdir: 1.0.2 + dev: true - tmpl@1.0.5: {} + /tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + dev: true - to-regex-range@5.0.1: + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 - toidentifier@1.0.1: {} + /toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} - tr46@0.0.3: {} + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - tree-kill@1.2.2: {} + /tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + dev: true - ts-api-utils@1.4.0(typescript@5.6.3): + /ts-api-utils@1.4.0(typescript@5.1.3): + resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' dependencies: - typescript: 5.6.3 + typescript: 5.1.3 + dev: true - ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)))(typescript@5.6.3): + /ts-jest@29.1.0(@babel/core@7.26.0)(jest@29.5.0)(typescript@5.1.3): + resolution: {integrity: sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true dependencies: + '@babel/core': 7.26.0 bs-logger: 0.2.6 - ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.17.6)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + jest: 29.5.0(@types/node@20.3.1)(ts-node@10.9.1) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.6.3 - typescript: 5.6.3 + typescript: 5.1.3 yargs-parser: 21.1.1 - optionalDependencies: - '@babel/core': 7.26.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.0) + dev: true - ts-loader@9.5.1(typescript@5.6.3)(webpack@5.96.1): + /ts-loader@9.4.3(typescript@5.1.3)(webpack@5.96.1): + resolution: {integrity: sha512-n3hBnm6ozJYzwiwt5YRiJZkzktftRpMiBApHaJPoWLA+qetQBAXkHqCLM6nwSdRDimqVtA5ocIkcTRLMTt7yzA==} + engines: {node: '>=12.0.0'} + peerDependencies: + typescript: '*' + webpack: ^5.0.0 dependencies: chalk: 4.1.2 enhanced-resolve: 5.17.1 micromatch: 4.0.8 semver: 7.6.3 - source-map: 0.7.4 - typescript: 5.6.3 + typescript: 5.1.3 webpack: 5.96.1 + dev: true - ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3): + /ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3): + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.17.6 + '@types/node': 20.3.1 acorn: 8.14.0 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.6.3 + typescript: 5.1.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - tsconfig-paths-webpack-plugin@4.1.0: + /tsconfig-paths-webpack-plugin@4.0.1: + resolution: {integrity: sha512-m5//KzLoKmqu2MVix+dgLKq70MnFi8YL8sdzQZ6DblmCdfuq/y3OqvJd5vMndg2KEVCOeNz8Es4WVZhYInteLw==} + engines: {node: '>=10.13.0'} dependencies: chalk: 4.1.2 enhanced-resolve: 5.17.1 tsconfig-paths: 4.2.0 + dev: true - tsconfig-paths@4.2.0: + /tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} dependencies: json5: 2.2.3 minimist: 1.2.8 strip-bom: 3.0.0 + dev: true - tslib@2.7.0: {} + /tslib@2.5.3: + resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==} - tslib@2.8.1: {} + /tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - type-check@0.4.0: + /type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.2.1 + dev: true - type-detect@4.0.8: {} + /type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: true - type-fest@0.20.2: {} + /type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + dev: true - type-fest@0.21.3: {} + /type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + dev: true - type-is@1.6.18: + /type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} dependencies: media-typer: 0.3.0 mime-types: 2.1.35 - typedarray@0.0.6: {} + /typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typeorm-extension@3.6.3(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3))): + /typeorm-extension@3.6.3(typeorm@0.3.20): + resolution: {integrity: sha512-AE+8KqBphlBdVz5JS77o6LZzzi+b+YFFt8So4Qu/KRo/iynAwekrx98Oxuu3FAYNm6DUKDcubOBMZsJeiRvHkA==} + engines: {node: '>=14.0.0'} + hasBin: true + peerDependencies: + typeorm: ~0.3.0 dependencies: '@faker-js/faker': 8.4.1 consola: 3.2.3 @@ -7714,10 +6646,67 @@ snapshots: rapiq: 0.9.0 reflect-metadata: 0.2.2 smob: 1.5.0 - typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)) + typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1) yargs: 17.7.2 + dev: false - typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.2(@types/node@20.17.6)(typescript@5.6.3)): + /typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1): + resolution: {integrity: sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q==} + engines: {node: '>=16.13.0'} + hasBin: true + peerDependencies: + '@google-cloud/spanner': ^5.18.0 + '@sap/hana-client': ^2.12.25 + better-sqlite3: ^7.1.2 || ^8.0.0 || ^9.0.0 + hdb-pool: ^0.1.6 + ioredis: ^5.0.4 + mongodb: ^5.8.0 + mssql: ^9.1.1 || ^10.0.1 + mysql2: ^2.2.5 || ^3.0.1 + oracledb: ^6.3.0 + pg: ^8.5.1 + pg-native: ^3.0.0 + pg-query-stream: ^4.0.0 + redis: ^3.1.1 || ^4.0.0 + sql.js: ^1.4.0 + sqlite3: ^5.0.3 + ts-node: ^10.7.0 + typeorm-aurora-data-api-driver: ^2.0.0 + peerDependenciesMeta: + '@google-cloud/spanner': + optional: true + '@sap/hana-client': + optional: true + better-sqlite3: + optional: true + hdb-pool: + optional: true + ioredis: + optional: true + mongodb: + optional: true + mssql: + optional: true + mysql2: + optional: true + oracledb: + optional: true + pg: + optional: true + pg-native: + optional: true + pg-query-stream: + optional: true + redis: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + ts-node: + optional: true + typeorm-aurora-data-api-driver: + optional: true dependencies: '@sqltools/formatter': 1.2.5 app-root-path: 3.1.0 @@ -7727,80 +6716,175 @@ snapshots: dayjs: 1.11.13 debug: 4.3.7 dotenv: 16.4.5 - glob: 10.4.2 + glob: 10.4.5 mkdirp: 2.1.6 + mysql2: 3.11.4 + pg: 8.13.1 reflect-metadata: 0.2.2 sha.js: 2.4.11 + ts-node: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) tslib: 2.8.1 uuid: 9.0.1 yargs: 17.7.2 - optionalDependencies: - mysql2: 3.11.4 - pg: 8.13.1 - ts-node: 10.9.2(@types/node@20.17.6)(typescript@5.6.3) transitivePeerDependencies: - supports-color + dev: false - typescript@5.6.3: {} + /typescript@5.1.3: + resolution: {integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==} + engines: {node: '>=14.17'} + hasBin: true - uid@2.0.2: + /uid@2.0.2: + resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} + engines: {node: '>=8'} dependencies: '@lukeed/csprng': 1.1.0 - undici-types@6.19.8: {} - - universalify@2.0.1: {} + /universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + dev: true - unpipe@1.0.0: {} + /unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} - update-browserslist-db@1.1.1(browserslist@4.24.2): + /update-browserslist-db@1.1.1(browserslist@4.24.2): + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' dependencies: browserslist: 4.24.2 escalade: 3.2.0 picocolors: 1.1.1 + dev: true - uri-js@4.4.1: + /uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: punycode: 2.3.1 + dev: true + + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - util-deprecate@1.0.2: {} + /utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} - utils-merge@1.0.1: {} + /uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + dev: false - uuid@9.0.1: {} + /v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - v8-compile-cache-lib@3.0.1: {} + /v8-compile-cache@2.4.0: + resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==} + dev: true - v8-to-istanbul@9.3.0: + /v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} dependencies: '@jridgewell/trace-mapping': 0.3.25 '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 + dev: true - validator@13.12.0: {} + /validator@13.12.0: + resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} + engines: {node: '>= 0.10'} - vary@1.1.2: {} + /vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} - walker@1.0.8: + /walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} dependencies: makeerror: 1.0.12 + dev: true - watchpack@2.4.2: + /watchpack@2.4.2: + resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==} + engines: {node: '>=10.13.0'} dependencies: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 + dev: true - wcwidth@1.0.1: + /wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} dependencies: defaults: 1.0.4 + dev: true + + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - webidl-conversions@3.0.1: {} + /webpack-node-externals@3.0.0: + resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} + engines: {node: '>=6'} + dev: true - webpack-node-externals@3.0.0: {} + /webpack-sources@3.2.3: + resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} + engines: {node: '>=10.13.0'} + dev: true - webpack-sources@3.2.3: {} + /webpack@5.87.0: + resolution: {integrity: sha512-GOu1tNbQ7p1bDEoFRs2YPcfyGs8xq52yyPBZ3m2VGnXGtV9MxjrkABHm4V9Ia280OefsSLzvbVoXcfLxjKY/Iw==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.6 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.14.0 + acorn-import-assertions: 1.9.0(acorn@8.14.0) + browserslist: 4.24.2 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.17.1 + es-module-lexer: 1.5.4 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.2.1 + terser-webpack-plugin: 5.3.10(webpack@5.87.0) + watchpack: 2.4.2 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + dev: true - webpack@5.96.1: + /webpack@5.96.1: + resolution: {integrity: sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.6 @@ -7829,56 +6913,97 @@ snapshots: - '@swc/core' - esbuild - uglify-js + dev: true - whatwg-url@5.0.0: + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 - which@2.0.2: + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true dependencies: isexe: 2.0.0 - word-wrap@1.2.5: {} - - wrap-ansi@6.2.0: + /windows-release@4.0.0: + resolution: {integrity: sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg==} + engines: {node: '>=10'} dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 + execa: 4.1.0 + dev: true - wrap-ansi@7.0.0: + /word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + dev: true + + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - wrap-ansi@8.1.0: + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} dependencies: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 + dev: false - wrappy@1.0.2: {} + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true - write-file-atomic@4.0.2: + /write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} dependencies: imurmurhash: 0.1.4 signal-exit: 3.0.7 + dev: true + + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} - xtend@4.0.2: {} + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} - y18n@5.0.8: {} + /yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + dev: true - yallist@3.1.1: {} + /yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + dev: true - yaml@2.6.0: {} + /yaml@2.6.0: + resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==} + engines: {node: '>= 14'} + hasBin: true + dev: false - yargs-parser@20.2.9: {} + /yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + dev: false - yargs-parser@21.1.1: {} + /yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} - yargs@16.2.0: + /yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} dependencies: cliui: 7.0.4 escalade: 3.2.0 @@ -7887,8 +7012,11 @@ snapshots: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 20.2.9 + dev: false - yargs@17.7.2: + /yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} dependencies: cliui: 8.0.1 escalade: 3.2.0 @@ -7898,6 +7026,11 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yn@3.1.1: {} + /yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} - yocto-queue@0.1.0: {} + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: true diff --git a/src/app.module.ts b/src/app.module.ts index 2e41704..968238a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -15,6 +15,7 @@ import { DatabaseModule } from './database/database.module'; import { EnrollmentModule } from './enrollment/enrollment.module'; import { ExamModule } from './exam/exam.module'; import { FileModule } from './file/file.module'; +import { ProgressModule } from './progress/progress.module'; import { databaseConfig } from './shared/configs/database.config'; import { dotenvConfig } from './shared/configs/dotenv.config'; import { GLOBAL_CONFIG } from './shared/constants/global-config.constant'; @@ -58,6 +59,7 @@ const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); FileModule, ExamModule, EnrollmentModule, + ProgressModule, ], controllers: [AppController], providers: [ diff --git a/src/chapter/chapter.entity.ts b/src/chapter/chapter.entity.ts index 6d6e5d4..6b63a1d 100644 --- a/src/chapter/chapter.entity.ts +++ b/src/chapter/chapter.entity.ts @@ -1,10 +1,12 @@ import { CourseModule } from 'src/course-module/course-module.entity'; +import { Progress } from 'src/progress/progress.entity'; import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, + OneToMany, PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; @@ -32,10 +34,12 @@ export class Chapter { }) @JoinColumn({ name: 'module_id' }) module: CourseModule; - @Column({ name: 'module_id' }) moduleId: string; + @OneToMany(() => Progress, (progress) => progress.chapter) + progresses: Progress[]; + @Column({ type: String, nullable: false, diff --git a/src/enrollment/dtos/create-enrollment.dto.ts b/src/enrollment/dtos/create-enrollment.dto.ts index c8f6d97..d1ca343 100644 --- a/src/enrollment/dtos/create-enrollment.dto.ts +++ b/src/enrollment/dtos/create-enrollment.dto.ts @@ -5,7 +5,6 @@ import { IsNotEmpty, IsNumber, IsOptional, - IsString, IsUUID, } from 'class-validator'; import { EnrollmentStatus } from '../enums/enrollment-status.enum'; @@ -56,7 +55,7 @@ export class CreateEnrollmentDto { }) status?: EnrollmentStatus; - @IsNotEmpty() + @IsOptional() @ApiProperty({ description: 'Enrollment date', type: Date, diff --git a/src/enrollment/enrollment.entity.ts b/src/enrollment/enrollment.entity.ts index d3479d3..4396989 100644 --- a/src/enrollment/enrollment.entity.ts +++ b/src/enrollment/enrollment.entity.ts @@ -1,4 +1,5 @@ import { Course } from 'src/course/course.entity'; +import { Progress } from 'src/progress/progress.entity'; import { User } from 'src/user/user.entity'; import { Column, @@ -6,6 +7,7 @@ import { Entity, JoinColumn, ManyToOne, + OneToMany, PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; @@ -24,6 +26,9 @@ export class Enrollment { @JoinColumn({ name: 'course_id' }) course: Course; + @OneToMany(() => Progress, (progress) => progress.enrollment) + progresses: Progress[]; + @Column({ type: 'enum', enum: EnrollmentStatus, diff --git a/src/enrollment/enrollment.service.ts b/src/enrollment/enrollment.service.ts index 22fd237..40e8468 100644 --- a/src/enrollment/enrollment.service.ts +++ b/src/enrollment/enrollment.service.ts @@ -1,8 +1,4 @@ -import { - BadRequestException, - Injectable, - NotFoundException, -} from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { createPagination } from 'src/shared/pagination'; import { FindOneOptions, FindOptionsWhere, ILike, Repository } from 'typeorm'; diff --git a/src/progress/dtos/create-progress.dto.ts b/src/progress/dtos/create-progress.dto.ts new file mode 100644 index 0000000..35d67db --- /dev/null +++ b/src/progress/dtos/create-progress.dto.ts @@ -0,0 +1,60 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + IsEnum, + IsNotEmpty, + IsNumber, + IsOptional, + IsUUID, +} from 'class-validator'; +import { ProgressStatus } from '../enums/progress-status.enum'; + +export class CreateProgressDto { + @IsNotEmpty() + @IsUUID() + @ApiProperty({ + description: 'Enrollment ID', + type: String, + }) + enrollmentId: string; + + @IsNotEmpty() + @IsUUID() + @ApiProperty({ + description: 'Chapter ID', + type: String, + }) + chapterId: string; + + @IsOptional() + @IsEnum(ProgressStatus) + @ApiProperty({ + description: 'Progress status', + enum: ProgressStatus, + default: ProgressStatus.ACTIVE, + }) + status?: ProgressStatus; + + @IsOptional() + @IsNumber() + @ApiProperty({ + description: 'Watch time', + type: Number, + default: 0, + }) + watchTime?: number; + + @IsOptional() + @ApiProperty({ + description: 'Last accessed date', + type: Date, + default: new Date(), + }) + lastAccessedAt?: Date; + + @IsOptional() + @ApiProperty({ + description: 'Completion date', + type: Date, + }) + completedAt?: Date; +} diff --git a/src/progress/dtos/progress-response.dto.ts b/src/progress/dtos/progress-response.dto.ts new file mode 100644 index 0000000..2f9181a --- /dev/null +++ b/src/progress/dtos/progress-response.dto.ts @@ -0,0 +1,92 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { ChapterResponseDto } from 'src/chapter/dtos/chapter-response.dto'; +import { EnrollmentResponseDto } from 'src/enrollment/dtos/enrollment-response.dto'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { ProgressStatus } from '../enums/progress-status.enum'; +import { Progress } from '../progress.entity'; + +export class ProgressResponseDto { + @ApiProperty({ + description: 'Progress ID', + type: String, + }) + id: string; + + @ApiProperty({ + description: 'Enrollment data', + type: EnrollmentResponseDto, + }) + enrollment: EnrollmentResponseDto; + + @ApiProperty({ + description: 'Chapter data', + type: ChapterResponseDto, + }) + chapter: ChapterResponseDto; + + @ApiProperty({ + description: 'Progress status', + enum: ProgressStatus, + }) + status: ProgressStatus; + + @ApiProperty({ + description: 'Watch time', + type: Number, + }) + watchTime: number; + + @ApiProperty({ + description: 'Last accessed date', + type: Date, + }) + lastAccessedAt: Date; + + @ApiProperty({ + description: 'Completion date', + type: Date, + nullable: true, + }) + completedAt: Date; + + @ApiProperty({ + description: 'Created date', + type: Date, + }) + createdAt: Date; + + @ApiProperty({ + description: 'Updated date', + type: Date, + }) + updatedAt: Date; + + constructor(progress: Progress) { + this.id = progress.id; + this.enrollment = new EnrollmentResponseDto(progress.enrollment); + this.chapter = new ChapterResponseDto(progress.chapter); + this.status = progress.status; + this.watchTime = progress.watchTime; + this.lastAccessedAt = progress.lastAccessedAt; + this.completedAt = progress.completedAt; + this.createdAt = progress.createdAt; + } +} + +export class PaginatedProgressResponseDto extends PaginatedResponse( + ProgressResponseDto, +) { + constructor( + progress: Progress[], + total: number, + page: number, + limit: number, + ) { + super( + progress.map((progress) => new ProgressResponseDto(progress)), + total, + page, + limit, + ); + } +} diff --git a/src/progress/dtos/update-progress.dto.ts b/src/progress/dtos/update-progress.dto.ts new file mode 100644 index 0000000..88ee423 --- /dev/null +++ b/src/progress/dtos/update-progress.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateProgressDto } from './create-progress.dto'; + +export class UpdateProgressDto extends PartialType(CreateProgressDto) {} diff --git a/src/progress/enums/progress-status.enum.ts b/src/progress/enums/progress-status.enum.ts new file mode 100644 index 0000000..818e3cb --- /dev/null +++ b/src/progress/enums/progress-status.enum.ts @@ -0,0 +1,5 @@ +export enum ProgressStatus { + ACTIVE = 'active', + COMPLETED = 'completed', + DROPPED = 'dropped', +} diff --git a/src/progress/progress.controller.ts b/src/progress/progress.controller.ts new file mode 100644 index 0000000..d3873ed --- /dev/null +++ b/src/progress/progress.controller.ts @@ -0,0 +1,139 @@ +import { + BadRequestException, + Body, + Controller, + Delete, + Get, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, +} from '@nestjs/common'; +import { + ApiBearerAuth, + ApiParam, + ApiQuery, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { CreateProgressDto } from './dtos/create-progress.dto'; +import { + PaginatedProgressResponseDto, + ProgressResponseDto, +} from './dtos/progress-response.dto'; +import { UpdateProgressDto } from './dtos/update-progress.dto'; +import { ProgressService } from './progress.service'; + +@Controller('progress') +@ApiTags('Progress') +@ApiBearerAuth() +@Injectable() +export class ProgressController { + constructor(private readonly progressService: ProgressService) {} + + @Get() + @ApiResponse({ + status: HttpStatus.OK, + type: ProgressResponseDto, + description: 'Get all progress', + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findAll( + @Query() query: PaginateQueryDto, + ): Promise { + console.log(query); + return this.progressService.findAll(query); + } + + @Get(':id') + @ApiParam({ + name: 'id', + type: String, + description: 'Progress ID', + }) + async findOne( + @Param('id', ParseUUIDPipe) id: string, + ): Promise { + return this.progressService.findOne(id, { where: { id } }); + } + + @Post() + @Roles(Role.STUDENT) + @ApiResponse({ + status: HttpStatus.CREATED, + type: ProgressResponseDto, + description: 'Create a progress', + }) + async create( + @Body() createProgressDto: CreateProgressDto, + @Req() req: AuthenticatedRequest, + ): Promise { + console.log(createProgressDto); + return this.progressService.create(createProgressDto); + } + + @Patch(':id') + @Roles(Role.STUDENT) + @ApiParam({ + name: 'id', + type: String, + description: 'Progress ID', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: ProgressResponseDto, + description: 'Update a progress', + }) + async update( + @Param('id', ParseUUIDPipe) id: string, + @Body() updateProgressDto: UpdateProgressDto, + @Req() req: AuthenticatedRequest, + ): Promise { + return this.progressService.update(id, updateProgressDto); + } + + @Delete(':id') + @Roles(Role.STUDENT) + @ApiParam({ + name: 'id', + type: String, + description: 'Progress ID', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: ProgressResponseDto, + description: 'Delete a progress', + }) + async delete( + @Param('id', ParseUUIDPipe) id: string, + @Req() req: AuthenticatedRequest, + ): Promise { + return this.progressService.remove(id); + } +} diff --git a/src/progress/progress.entity.ts b/src/progress/progress.entity.ts new file mode 100644 index 0000000..ca1639e --- /dev/null +++ b/src/progress/progress.entity.ts @@ -0,0 +1,70 @@ +import { Chapter } from 'src/chapter/chapter.entity'; +import { Enrollment } from 'src/enrollment/enrollment.entity'; +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; +import { ProgressStatus } from './enums/progress-status.enum'; + +@Entity() +export class Progress { + @PrimaryGeneratedColumn('uuid') + id: string; + + @ManyToOne(() => Enrollment, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'enrollment_id' }) + enrollment: Enrollment; + @Column({ name: 'enrollment_id' }) + enrollmentId: string; + + @ManyToOne(() => Chapter, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'chapter_id' }) + chapter: Chapter; + @Column({ name: 'chapter_id' }) + chapterId: string; + + @Column({ + type: 'enum', + enum: ProgressStatus, + default: ProgressStatus.ACTIVE, + }) + status: ProgressStatus; + + @Column({ + type: 'decimal', + precision: 5, + scale: 2, + default: 0, + }) + watchTime: number; + + @Column({ + type: 'timestamp', + name: 'last_accessed_at', + }) + lastAccessedAt: Date; + + @Column({ + type: 'timestamp', + name: 'completed_at', + nullable: true, + }) + completedAt: Date; + + @CreateDateColumn({ + type: 'timestamp', + name: 'created_at', + }) + createdAt: Date; + + @UpdateDateColumn({ + type: 'timestamp', + name: 'updated_at', + }) + updatedAt: Date; +} diff --git a/src/progress/progress.module.ts b/src/progress/progress.module.ts new file mode 100644 index 0000000..7dff459 --- /dev/null +++ b/src/progress/progress.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ProgressController } from './progress.controller'; +import { Progress } from './progress.entity'; +import { ProgressService } from './progress.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Progress])], + controllers: [ProgressController], + providers: [ProgressService], +}) +export class ProgressModule {} diff --git a/src/progress/progress.service.ts b/src/progress/progress.service.ts new file mode 100644 index 0000000..f9aab36 --- /dev/null +++ b/src/progress/progress.service.ts @@ -0,0 +1,83 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { createPagination } from 'src/shared/pagination'; +import { FindOneOptions, FindOptionsWhere, Repository } from 'typeorm'; +import { CreateProgressDto } from './dtos/create-progress.dto'; +import { PaginatedProgressResponseDto } from './dtos/progress-response.dto'; +import { UpdateProgressDto } from './dtos/update-progress.dto'; +import { ProgressStatus } from './enums/progress-status.enum'; +import { Progress } from './progress.entity'; + +@Injectable() +export class ProgressService { + constructor( + @InjectRepository(Progress) + private readonly progressRepository: Repository, + ) {} + + async findAll({ + page = 1, + limit = 20, + }: { + page?: number; + limit?: number; + }): Promise { + const { find } = await createPagination(this.progressRepository, { + page, + limit, + }); + + const progress = await find({ + relations: { + enrollment: true, + chapter: true, + }, + }).run(); + + return progress; + } + + async findOne( + id: string, + options: FindOneOptions, + ): Promise { + const baseWhere = options.where as FindOptionsWhere; + const whereCondition = { ...baseWhere, id }; + + const progress = await this.progressRepository.findOne({ + where: whereCondition, + relations: { + enrollment: true, + chapter: true, + }, + }); + + if (!progress) { + throw new NotFoundException('Progress not found'); + } + + return progress; + } + + async create(data: CreateProgressDto): Promise { + const progress = this.progressRepository.create(data); + await this.progressRepository.save(progress); + + return progress; + } + + async update(id: string, data: UpdateProgressDto): Promise { + const progress = await this.findOne(id, { where: { id } }); + Object.assign(progress, data); + await this.progressRepository.save(progress); + + return progress; + } + + async remove(id: string): Promise { + const progress = await this.findOne(id, { where: { id } }); + await this.progressRepository.remove(progress); + + return progress; + } +} diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index 0fa4583..5e26095 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -5,11 +5,12 @@ import { Chapter } from 'src/chapter/chapter.entity'; import { CourseModule } from 'src/course-module/course-module.entity'; import { Course } from 'src/course/course.entity'; import { Enrollment } from 'src/enrollment/enrollment.entity'; +import { Exam } from 'src/exam/exam.entity'; +import { Progress } from 'src/progress/progress.entity'; import { UserStreak } from 'src/user-streak/user-streak.entity'; import { User } from 'src/user/user.entity'; import { DataSource, DataSourceOptions } from 'typeorm'; import { GLOBAL_CONFIG } from '../constants/global-config.constant'; -import { Exam } from 'src/exam/exam.entity'; const configService = new ConfigService(); @@ -29,7 +30,8 @@ export const databaseConfig: DataSourceOptions = { CourseModule, Chapter, Enrollment, - Exam + Exam, + Progress, ], }; diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index ca3f898..72681bf 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -2,60 +2,52 @@ import { Course } from 'src/course/course.entity'; import { Enrollment } from 'src/enrollment/enrollment.entity'; import { Role } from 'src/shared/enums/roles.enum'; import { -<<<<<<< HEAD Column, CreateDateColumn, Entity, OneToMany, PrimaryGeneratedColumn, UpdateDateColumn, -======= - Column, - PrimaryGeneratedColumn, - CreateDateColumn, - UpdateDateColumn, ->>>>>>> 95ec4da3bae6ba83f175becb60440f6d278b863e } from 'typeorm'; @Entity() export class User { - @PrimaryGeneratedColumn('uuid') - id: string; + @PrimaryGeneratedColumn('uuid') + id: string; - @Column({ - nullable: false, - unique: true, - }) - username: string; + @Column({ + nullable: false, + unique: true, + }) + username: string; - @Column({ - nullable: false, - }) - fullname: string; + @Column({ + nullable: false, + }) + fullname: string; - @Column({ - type: 'enum', - enum: Role, - nullable: false, - default: Role.STUDENT, - }) - role: Role; + @Column({ + type: 'enum', + enum: Role, + nullable: false, + default: Role.STUDENT, + }) + role: Role; - @Column({ - nullable: false, - unique: true, - }) - password: string; + @Column({ + nullable: false, + unique: true, + }) + password: string; - @Column({ - nullable: false, - unique: true, - }) - email: string; + @Column({ + nullable: false, + unique: true, + }) + email: string; - @OneToMany(() => Course, (course) => course.teacher) - courses: Course[]; + @OneToMany(() => Course, (course) => course.teacher) + courses: Course[]; -<<<<<<< HEAD @OneToMany(() => Enrollment, (enrollment) => enrollment.user) enrollments: Enrollment[]; @@ -64,22 +56,15 @@ export class User { nullable: false, }) createdAt: Date; -======= - @CreateDateColumn({ - type: 'timestamp with time zone', - nullable: false, - }) - createdAt: Date; ->>>>>>> 95ec4da3bae6ba83f175becb60440f6d278b863e - @UpdateDateColumn({ - type: 'timestamp with time zone', - nullable: false, - }) - updatedAt: Date; + @UpdateDateColumn({ + type: 'timestamp with time zone', + nullable: false, + }) + updatedAt: Date; - @Column({ - nullable: true, - }) - profileKey: string; + @Column({ + nullable: true, + }) + profileKey: string; } From 8223ba03c199493589562713a1cc56ac16c26a2b Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sun, 17 Nov 2024 11:40:51 +0700 Subject: [PATCH 057/155] feat: create question-option --- src/question-option/dtos/create-question-option.dto.ts | 0 src/question-option/dtos/update-question-option.dto.ts | 0 src/question-option/question-option.controller.ts | 0 src/question-option/question-option.entity.ts | 0 src/question-option/question-option.module.ts | 0 src/question-option/question-option.providers.ts | 0 src/question-option/question-option.service.ts | 0 7 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/question-option/dtos/create-question-option.dto.ts create mode 100644 src/question-option/dtos/update-question-option.dto.ts create mode 100644 src/question-option/question-option.controller.ts create mode 100644 src/question-option/question-option.entity.ts create mode 100644 src/question-option/question-option.module.ts create mode 100644 src/question-option/question-option.providers.ts create mode 100644 src/question-option/question-option.service.ts diff --git a/src/question-option/dtos/create-question-option.dto.ts b/src/question-option/dtos/create-question-option.dto.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/question-option/dtos/update-question-option.dto.ts b/src/question-option/dtos/update-question-option.dto.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/question-option/question-option.controller.ts b/src/question-option/question-option.controller.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/question-option/question-option.entity.ts b/src/question-option/question-option.entity.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/question-option/question-option.module.ts b/src/question-option/question-option.module.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/question-option/question-option.providers.ts b/src/question-option/question-option.providers.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/question-option/question-option.service.ts b/src/question-option/question-option.service.ts new file mode 100644 index 0000000..e69de29 From 501e6a1063c5a8f2b4d2a8f47cff9523576d7cc2 Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Sun, 17 Nov 2024 12:45:51 +0700 Subject: [PATCH 058/155] feat: implement course ownership validation for chapters and course modules across services and controllers --- src/app.module.ts | 5 ++ src/chapter/chapter.controller.ts | 31 ++++---- src/chapter/chapter.module.ts | 11 +-- src/chapter/chapter.service.ts | 78 +++++++++++++++++-- src/course-module/course-module.controller.ts | 43 +++++----- src/course-module/course-module.module.ts | 5 +- src/course-module/course-module.service.ts | 61 ++++++++++++++- src/course/course.controller.ts | 21 +++-- src/course/course.module.ts | 11 +-- src/course/course.service.ts | 6 ++ .../decorators/course-ownership.decorator.ts | 5 +- src/shared/guards/course-ownership.guard.ts | 3 + 12 files changed, 207 insertions(+), 73 deletions(-) diff --git a/src/app.module.ts b/src/app.module.ts index e3d6da5..101b651 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -24,6 +24,7 @@ import { ExamModule } from './exam/exam.module'; import { Course } from './course/course.entity'; import { CourseModule as CourseModuleEntity } from './course-module/course-module.entity'; import { Chapter } from './chapter/chapter.entity'; +import { CourseOwnershipGuard } from './shared/guards/course-ownership.guard'; const forFeatures = TypeOrmModule.forFeature([ User, @@ -74,6 +75,10 @@ const forFeatures = TypeOrmModule.forFeature([ provide: APP_GUARD, useClass: RolesGuard, }, + { + provide: APP_GUARD, + useClass: CourseOwnershipGuard, + } ], }) export class AppModule { } diff --git a/src/chapter/chapter.controller.ts b/src/chapter/chapter.controller.ts index b37dcea..69d4be1 100644 --- a/src/chapter/chapter.controller.ts +++ b/src/chapter/chapter.controller.ts @@ -12,6 +12,7 @@ import { Post, Query, Req, + UseGuards, } from '@nestjs/common'; import { ApiBearerAuth, @@ -33,6 +34,7 @@ import { CreateChapterDto } from './dtos/create-chapter.dto'; import { UpdateChapterDto } from './dtos/update-chapter.dto'; import { CourseOwnership } from 'src/shared/decorators/course-ownership.decorator'; import { CourseModuleService } from 'src/course-module/course-module.service'; +import { Course } from 'src/course/course.entity'; @Controller('chapter') @ApiTags('Chapters') @@ -61,9 +63,16 @@ export class ChapterController { description: 'Items per page', }) async findAll( + @Req() request: AuthenticatedRequest, @Query() query: PaginateQueryDto, ): Promise { - return this.chapterService.findAll(query); + return this.chapterService.findAll({ + page: query.page, + limit: query.limit, + search: query.search, + userId: request.user.id, + role: request.user.role, + }); } @Get(':id') @@ -81,13 +90,7 @@ export class ChapterController { @Req() request: AuthenticatedRequest, @Param('id', ParseUUIDPipe) id: string, ): Promise { - const courseModule = await this.courseModuleService.findOne(id, { where: { id } }); - - if (!courseModule && courseModule.course.teacher.id !== request.user.id) { - throw new BadRequestException('Course Module not found'); - } - - return this.chapterService.findOne(id, { where: { id } }); + return this.chapterService.findOne(request.user.id, request.user.role, { where: { id } }); } @Post() @@ -98,8 +101,12 @@ export class ChapterController { description: 'Create a chapter', }) async create( + @Req() request: AuthenticatedRequest, @Body() createChapterDto: CreateChapterDto, ): Promise { + if (createChapterDto.moduleId != null) { + await this.courseModuleService.validateOwnership(createChapterDto.moduleId, request.user.id); + } return this.chapterService.create(createChapterDto); } @@ -120,13 +127,9 @@ export class ChapterController { @Param('id', ParseUUIDPipe) id: string, @Body() updateChapterDto: UpdateChapterDto, ): Promise { - if (updateChapterDto.moduleId != null) { - const courseModule = await this.courseModuleService.findOne(id, { where: { id } }); - - if (!courseModule && courseModule.course.teacher.id !== request.user.id) { - throw new BadRequestException('Course Module not found'); + if (updateChapterDto.moduleId != null) { + await this.courseModuleService.validateOwnership(updateChapterDto.moduleId, request.user.id); } - } return this.chapterService.update(id, updateChapterDto); } diff --git a/src/chapter/chapter.module.ts b/src/chapter/chapter.module.ts index 326f7af..024c079 100644 --- a/src/chapter/chapter.module.ts +++ b/src/chapter/chapter.module.ts @@ -5,15 +5,10 @@ import { Chapter } from './chapter.entity'; import { chapterProviders } from './chapter.provider'; import { ChapterService } from './chapter.service'; import { CourseModuleModule } from 'src/course-module/course-module.module'; -import { CourseOwnershipGuard } from 'src/shared/guards/course-ownership.guard'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Course } from 'src/course/course.entity'; -import { CourseModule as CourseModuleEntity } from 'src/course-module/course-module.entity'; @Module({ - imports: [DatabaseModule, CourseModuleModule, TypeOrmModule.forFeature([Chapter, Course, CourseModuleEntity])], - controllers: [ChapterController], - providers: [...chapterProviders, ChapterService, CourseOwnershipGuard, - ], + imports: [DatabaseModule, CourseModuleModule], + controllers: [ChapterController], + providers: [...chapterProviders, ChapterService], exports: [ChapterService], }) export class ChapterModule { } diff --git a/src/chapter/chapter.service.ts b/src/chapter/chapter.service.ts index 7550ee4..a7b94d7 100644 --- a/src/chapter/chapter.service.ts +++ b/src/chapter/chapter.service.ts @@ -10,6 +10,7 @@ import { Chapter } from './chapter.entity'; import { PaginatedChapterResponseDto } from './dtos/chapter-response.dto'; import { CreateChapterDto } from './dtos/create-chapter.dto'; import { UpdateChapterDto } from './dtos/update-chapter.dto'; +import { CourseStatus, Role } from 'src/shared/enums'; @Injectable() export class ChapterService { @@ -22,10 +23,14 @@ export class ChapterService { page = 1, limit = 20, search = '', + userId, + role, }: { page?: number; limit?: number; search?: string; + userId: string; + role: Role; }): Promise { const { find } = await createPagination(this.chapterRepository, { page, @@ -33,7 +38,7 @@ export class ChapterService { }); const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; - const whereCondition = { ...baseSearch }; + const whereCondition = this.buildWhereCondition(userId, role, baseSearch); const chapters = await find({ where: whereCondition, @@ -46,11 +51,12 @@ export class ChapterService { } async findOne( - id: string, + userId: string, + role: Role, options: FindOneOptions, ): Promise { const baseWhere = options.where as FindOptionsWhere; - const whereCondition = { ...baseWhere, id }; + const whereCondition = this.buildWhereCondition(userId, role, baseWhere); const chapter = await this.chapterRepository.findOne({ where: whereCondition, @@ -113,7 +119,7 @@ export class ChapterService { id: string, updateChapterDto: UpdateChapterDto, ): Promise { - const chapter = await this.findOne(id, { where: { id } }); + const chapter = await this.chapterRepository.findOne({ where: { id } }); if (!chapter) { throw new NotFoundException('Chapter not found'); @@ -126,7 +132,9 @@ export class ChapterService { updateChapterDto.orderIndex !== chapter.orderIndex ) { const existingChapter = await this.chapterRepository.findOne({ - where: { orderIndex: updateChapterDto.orderIndex }, + where: { + moduleId: chapter.moduleId, + orderIndex: updateChapterDto.orderIndex }, }); if (existingChapter) { @@ -134,14 +142,14 @@ export class ChapterService { } } - this.chapterRepository.merge(chapter, updateChapterDto); + Object.assign(chapter, updateChapterDto); await this.chapterRepository.save(chapter); return chapter; } async remove(id: string): Promise { - const chapter = await this.findOne(id, { where: { id } }); + const chapter = await this.chapterRepository.findOne({ where: { id } }); if (!chapter) { throw new BadRequestException('Chapter not found'); @@ -176,4 +184,60 @@ export class ChapterService { ); } } + async validateOwnership(id: string, userId: string): Promise { + const chapter = await this.chapterRepository.findOne({ where: { id }, relations: { module: { course: { teacher: true } } } }); + if(!chapter) throw new NotFoundException('Chapter not found'); + if (chapter.module.course.teacher.id !== userId) + throw new BadRequestException('You can only access your own courses'); + } + private buildWhereCondition( + userId: string, + role: Role, + baseCondition: FindOptionsWhere = {} + ) + { + const conditions: Record< + Role, + () => FindOptionsWhere | FindOptionsWhere[] + > = { + [Role.STUDENT]: () => ({ + ...baseCondition, + module: { + course: { + status: CourseStatus.PUBLISHED, + }, + }, + }), + [Role.TEACHER]: () => [ + { + ...baseCondition, + module: { + course: { + status: CourseStatus.PUBLISHED, + }, + }, + }, + { + ...baseCondition, + module: { + course: { + teacher: { + id: userId, + }, + }, + }, + }, + ], + [Role.ADMIN]: () => baseCondition, + }; + + const buildCondition = conditions[role]; + + if (!buildCondition) { + throw new BadRequestException('Invalid role'); + } + + return buildCondition(); + } + } diff --git a/src/course-module/course-module.controller.ts b/src/course-module/course-module.controller.ts index e81a8d9..27e8451 100644 --- a/src/course-module/course-module.controller.ts +++ b/src/course-module/course-module.controller.ts @@ -5,14 +5,12 @@ import { Delete, Get, HttpStatus, - Injectable, Param, ParseUUIDPipe, Patch, Post, Query, Req, - UseGuards, } from '@nestjs/common'; import { ApiBearerAuth, @@ -31,14 +29,13 @@ import { } from './dtos/course-module-response.dto'; import { CreateCourseModuleDto } from './dtos/create-course-module.dto'; import { UpdateCourseModuleDto } from './dtos/update-course-module.dto'; -import { CourseOwnership } from 'src/shared/decorators/course-ownership.decorator'; import { CourseService } from 'src/course/course.service'; -import { CourseOwnershipGuard } from 'src/shared/guards/course-ownership.guard'; +import { CourseOwnership } from 'src/shared/decorators/course-ownership.decorator'; +import { Roles } from 'src/shared/decorators/role.decorator'; @Controller('course-module') @ApiTags('Course Modules') @ApiBearerAuth() -@Injectable() export class CourseModuleController { constructor(private readonly courseModuleService: CourseModuleService, private readonly courseService: CourseService) { } @@ -68,12 +65,15 @@ export class CourseModuleController { description: 'Search by title', }) async findAll( + @Req() request: AuthenticatedRequest, @Query() query: PaginateQueryDto, ): Promise { return this.courseModuleService.findAll({ page: query.page, limit: query.limit, search: query.search, + userId: request.user.id, + role: request.user.role, }); } @@ -89,11 +89,10 @@ export class CourseModuleController { description: 'Get a course module', }) async findOne( + @Req() request: AuthenticatedRequest, @Param('id', new ParseUUIDPipe()) id: string, ): Promise { - return this.courseModuleService.findOne(id, { - where: { id }, - }); + return this.courseModuleService.findOne(request.user.id, request.user.role, {where: { id }}); } @Get('course/:courseId') @@ -109,12 +108,15 @@ export class CourseModuleController { isArray: true, }) async findByCourseId( + @Req() request: AuthenticatedRequest, @Param('courseId', new ParseUUIDPipe()) courseId: string, ): Promise { + await this.courseService.validateOwnership(courseId, request.user.id); return this.courseModuleService.findByCourseId(courseId); } @Post() + @Roles(Role.TEACHER) @ApiResponse({ status: HttpStatus.CREATED, type: CourseModuleResponseDto, @@ -124,39 +126,40 @@ export class CourseModuleController { @Req() request: AuthenticatedRequest, @Body() createCourseModuleDto: CreateCourseModuleDto, ): Promise { - const course = await this.courseService.findOne(request.user.id, Role.TEACHER, { where: { id: createCourseModuleDto.courseId } }); - - if (!course) { - throw new BadRequestException('Course not found'); + if (createCourseModuleDto.courseId != null) { + await this.courseService.validateOwnership(createCourseModuleDto.courseId, request.user.id); } return this.courseModuleService.create(createCourseModuleDto); } @Patch(':id') - @CourseOwnership({ - adminDraftOnly: true - }) @ApiParam({ name: 'id', type: String, description: 'Course Module ID', }) + @CourseOwnership({adminDraftOnly: true}) async update( @Req() request: AuthenticatedRequest, - @Param('id', new ParseUUIDPipe()) id: string, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, @Body() updateCourseModuleDto: UpdateCourseModuleDto, ): Promise { if (updateCourseModuleDto.courseId != null) { - const course = await this.courseService.findOne(request.user.id, request.user.role, { where: { id: updateCourseModuleDto.courseId } }); - if (!course) { - throw new BadRequestException('Course not found'); - } + await this.courseService.validateOwnership(updateCourseModuleDto.courseId, request.user.id); } return this.courseModuleService.update(id, updateCourseModuleDto); } @Delete(':id') + @CourseOwnership() @ApiParam({ name: 'id', type: String, diff --git a/src/course-module/course-module.module.ts b/src/course-module/course-module.module.ts index 96073fe..2ef8674 100644 --- a/src/course-module/course-module.module.ts +++ b/src/course-module/course-module.module.ts @@ -1,15 +1,14 @@ import { Module } from '@nestjs/common'; -import { CourseModule } from 'src/course/course.module'; import { DatabaseModule } from 'src/database/database.module'; import { CourseModuleController } from './course-module.controller'; import { courseModuleProviders } from './course-module.provider'; import { CourseModuleService } from './course-module.service'; +import { CourseModule } from 'src/course/course.module'; @Module({ imports: [DatabaseModule, CourseModule], controllers: [CourseModuleController], - providers: [...courseModuleProviders, CourseModuleService - ], + providers: [...courseModuleProviders, CourseModuleService], exports: [CourseModuleService], }) export class CourseModuleModule {} diff --git a/src/course-module/course-module.service.ts b/src/course-module/course-module.service.ts index 8b5db12..e7e5aca 100644 --- a/src/course-module/course-module.service.ts +++ b/src/course-module/course-module.service.ts @@ -10,6 +10,7 @@ import { CourseModule } from './course-module.entity'; import { PaginatedCourseModuleResponseDto } from './dtos/course-module-response.dto'; import { CreateCourseModuleDto } from './dtos/create-course-module.dto'; import { UpdateCourseModuleDto } from './dtos/update-course-module.dto'; +import { CourseStatus, Role } from 'src/shared/enums'; @Injectable() export class CourseModuleService { @@ -22,10 +23,14 @@ export class CourseModuleService { page = 1, limit = 20, search = '', + userId, + role, }: { page?: number; limit?: number; search?: string; + userId: string; + role: Role; }): Promise { const { find } = await createPagination(this.courseModuleRepository, { page, @@ -33,7 +38,7 @@ export class CourseModuleService { }); const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; - const whereCondition = { ...baseSearch }; + const whereCondition = this.buildWhereCondition(userId, role, baseSearch); const courseModules = await find({ where: whereCondition, @@ -46,11 +51,12 @@ export class CourseModuleService { } async findOne( - id: string, + userId: string, + role: Role, options: FindOneOptions, ): Promise { const baseWhere = options.where as FindOptionsWhere; - const whereCondition = { ...baseWhere, id }; + const whereCondition = this.buildWhereCondition(userId, role, baseWhere); const courseModule = await this.courseModuleRepository.findOne({ where: whereCondition, @@ -191,4 +197,53 @@ export class CourseModuleService { ); } } + async validateOwnership(id: string, userId: string): Promise { + const courseModule = await this.courseModuleRepository.findOne({ where: { id }, relations: { course: { teacher: true } } }); + if(!courseModule) throw new NotFoundException('Course Module not found'); + if (courseModule.course.teacher.id !== userId) + throw new BadRequestException('You can only access your own courses'); + } + private buildWhereCondition( + userId: string, + role: Role, + baseCondition: FindOptionsWhere = {} + ) + { + const conditions: Record< + Role, + () => FindOptionsWhere | FindOptionsWhere[] + > = { + [Role.STUDENT]: () => ({ + ...baseCondition, + course: { + status: CourseStatus.PUBLISHED, + }, + }), + [Role.TEACHER]: () => [ + { + ...baseCondition, + course: { + status: CourseStatus.PUBLISHED, + }, + }, + { + ...baseCondition, + course: { + teacher: { + id: userId, + }, + }, + }, + ], + [Role.ADMIN]: () => baseCondition, + }; + + const buildCondition = conditions[role]; + + if (!buildCondition) { + throw new BadRequestException('Invalid role'); + } + + return buildCondition(); + } } \ No newline at end of file diff --git a/src/course/course.controller.ts b/src/course/course.controller.ts index ab7ff24..9e52b20 100644 --- a/src/course/course.controller.ts +++ b/src/course/course.controller.ts @@ -33,6 +33,7 @@ import { UpdateCourseDto, } from './dtos/index'; import { CourseOwnership } from 'src/shared/decorators/course-ownership.decorator'; +import { CourseOwnershipGuard } from 'src/shared/guards/course-ownership.guard'; @Controller('course') @ApiTags('Course') @@ -138,9 +139,7 @@ export class CourseController { } @Patch(':id') - @CourseOwnership({ - adminDraftOnly: true - }) + @CourseOwnership({adminDraftOnly: true}) @ApiParam({ name: 'id', type: String, @@ -154,7 +153,13 @@ export class CourseController { async update( @Req() request: AuthenticatedRequest, @Body() updateCourseDto: UpdateCourseDto, - @Param('id', ParseUUIDPipe) id: string, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + )id: string, ): Promise { const category = await this.categoryService.findOne({ where: { id: updateCourseDto.categoryId }, @@ -183,7 +188,13 @@ export class CourseController { description: 'Delete course by id', }) async delete( - @Param('id', ParseUUIDPipe) id: string, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) id: string, ): Promise { await this.courseService.delete(id); } diff --git a/src/course/course.module.ts b/src/course/course.module.ts index eeb5ac1..f6b7f6a 100644 --- a/src/course/course.module.ts +++ b/src/course/course.module.ts @@ -4,18 +4,11 @@ import { DatabaseModule } from 'src/database/database.module'; import { CourseController } from './course.controller'; import { courseProviders } from './course.provider'; import { CourseService } from './course.service'; -import { CourseOwnershipGuard } from 'src/shared/guards/course-ownership.guard'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Course } from './course.entity'; -import { Chapter } from 'src/chapter/chapter.entity'; -import { CourseModule as CourseModuleEntity } from '../course-module/course-module.entity'; @Module({ - imports: [DatabaseModule, TypeOrmModule.forFeature([CourseModuleEntity, Course, Chapter]), CategoryModule], + imports: [DatabaseModule, CategoryModule], controllers: [CourseController], - providers: [...courseProviders, CourseService,CourseOwnershipGuard - - ], + providers: [...courseProviders, CourseService], exports: [CourseService], }) export class CourseModule { } diff --git a/src/course/course.service.ts b/src/course/course.service.ts index efa99da..36fb4f8 100644 --- a/src/course/course.service.ts +++ b/src/course/course.service.ts @@ -182,4 +182,10 @@ export class CourseService { ); } } + async validateOwnership(id: string, userId: string): Promise { + const course = await this.courseRepository.findOne({ where: { id }, relations: { teacher: true } }); + if(!course) throw new NotFoundException('Course not found'); + if (course.teacher.id !== userId) + throw new BadRequestException('You can only access your own courses'); + } } diff --git a/src/shared/decorators/course-ownership.decorator.ts b/src/shared/decorators/course-ownership.decorator.ts index cb50a4f..ddbe0c9 100644 --- a/src/shared/decorators/course-ownership.decorator.ts +++ b/src/shared/decorators/course-ownership.decorator.ts @@ -3,9 +3,6 @@ import { SetMetadata } from '@nestjs/common'; export const COURSE_OWNERSHIP_KEY = 'courseOwnership' as const; export const ADMIN_DRAFT_ONLY_KEY = 'adminDraftOnly' as const; -interface CourseAccessConfig { - adminDraftOnly?: boolean; -} type DecoratorFunction = ( target: object | Function, @@ -13,7 +10,7 @@ type DecoratorFunction = ( descriptor?: PropertyDescriptor ) => any; -export const CourseOwnership = (config: CourseAccessConfig = {}): DecoratorFunction => { +export const CourseOwnership = (config: {adminDraftOnly?: boolean;} = {}): DecoratorFunction => { return ( target: object | Function, propertyKey?: string | symbol, diff --git a/src/shared/guards/course-ownership.guard.ts b/src/shared/guards/course-ownership.guard.ts index de8a776..6a5f7c4 100644 --- a/src/shared/guards/course-ownership.guard.ts +++ b/src/shared/guards/course-ownership.guard.ts @@ -97,6 +97,9 @@ export class CourseOwnershipGuard implements CanActivate { } private validateTeacherAccess(userId: string, course: Course): boolean { + console.log(course.teacher.id); + console.log(course); + console.log(userId); if (course.teacher.id !== userId) { throw new UnauthorizedException('You can only access your own courses'); } From d3ebd40dec4dcdd117d08335ca0bb563fad3517d Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sun, 17 Nov 2024 13:34:21 +0700 Subject: [PATCH 059/155] feat: create question-option dto and api --- src/app.module.ts | 2 + src/exam-attempt/exam-attempt.service.dto.ts | 9 +- src/exam/exam.service.ts | 9 +- .../dtos/create-question-option.dto.ts | 36 +++ .../dtos/question-option-response.dto.ts | 87 +++++++ .../dtos/update-question-option.dto.ts | 6 + .../question-option.controller.ts | 211 +++++++++++++++ src/question-option/question-option.entity.ts | 51 ++++ src/question-option/question-option.module.ts | 19 ++ .../question-option.providers.ts | 11 + .../question-option.service.ts | 243 ++++++++++++++++++ src/question/question.entity.ts | 11 + src/question/question.service.ts | 22 +- src/shared/configs/database.config.ts | 2 + 14 files changed, 703 insertions(+), 16 deletions(-) create mode 100644 src/question-option/dtos/question-option-response.dto.ts diff --git a/src/app.module.ts b/src/app.module.ts index 73e8f1a..86595af 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -25,6 +25,7 @@ import { User } from './user/user.entity'; import { UserModule } from './user/user.module'; import { ExamAttemptModule } from './exam-attempt/exam-attempt.module'; import { QuestionModule } from './question/question.module'; +import { QuestionOptionModule } from './question-option/question-option.module'; const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); @@ -63,6 +64,7 @@ const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); ExamAttemptModule, QuestionModule, + QuestionOptionModule, ], controllers: [AppController], providers: [ diff --git a/src/exam-attempt/exam-attempt.service.dto.ts b/src/exam-attempt/exam-attempt.service.dto.ts index 024704e..6e0db6f 100644 --- a/src/exam-attempt/exam-attempt.service.dto.ts +++ b/src/exam-attempt/exam-attempt.service.dto.ts @@ -132,9 +132,16 @@ export class ExamAttemptService { ): Promise { const whereCondition = this.validateAndCreateCondition(request, ''); + const where = Array.isArray(whereCondition) + ? [ + { ...whereCondition[0], ...options.where }, + { ...whereCondition[1], ...options.where }, + ] + : { ...whereCondition, ...options.where }; + const exam = await this.examAttemptRepository.findOne({ ...options, - where: whereCondition, + where, relations: ['exam', 'user'], select: { user: this.selectPopulateUser(), diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts index f342a44..717c0fb 100644 --- a/src/exam/exam.service.ts +++ b/src/exam/exam.service.ts @@ -95,9 +95,16 @@ export class ExamService { ): Promise { const whereCondition = this.validateAndCreateCondition(request, ''); + const where = Array.isArray(whereCondition) + ? [ + { ...whereCondition[0], ...options.where }, + { ...whereCondition[1], ...options.where }, + ] + : { ...whereCondition, ...options.where }; + const exam = await this.examRepository.findOne({ ...options, - where: whereCondition, + where, relations: ['courseModule'], select: { courseModule: this.selectPopulateCourseModule(), diff --git a/src/question-option/dtos/create-question-option.dto.ts b/src/question-option/dtos/create-question-option.dto.ts index e69de29..81a958c 100644 --- a/src/question-option/dtos/create-question-option.dto.ts +++ b/src/question-option/dtos/create-question-option.dto.ts @@ -0,0 +1,36 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsNotEmpty } from 'class-validator'; + +export class CreateQuestionOptionDto { + @IsNotEmpty() + @ApiProperty({ + description: 'Question ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + questionId: string; + @IsNotEmpty() + @ApiProperty({ + description: 'Question option text', + type: String, + example: 'A. Rock', + }) + optionText: string; + + @IsNotEmpty() + @IsBoolean() + @ApiProperty({ + description: 'Is this question option correct?', + type: Boolean, + example: false, + }) + isCorrect: boolean; + + @IsNotEmpty() + @ApiProperty({ + description: 'ehy this question option correct or incorrect?', + type: String, + example: 'Rock is not biology.', + }) + explanation: string; +} diff --git a/src/question-option/dtos/question-option-response.dto.ts b/src/question-option/dtos/question-option-response.dto.ts new file mode 100644 index 0000000..431fe40 --- /dev/null +++ b/src/question-option/dtos/question-option-response.dto.ts @@ -0,0 +1,87 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { QuestionOption } from '../question-option.entity'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { Question } from 'src/question/question.entity'; + +export class QuestionOptionResponseDto { + @ApiProperty({ + description: 'Question option ID', + type: String, + example: '123e4567-e89b-12d3-a456-426614174000', + }) + id: string; + + @ApiProperty({ + description: 'Question Data', + type: Question, + example: { + id: '3a3013bb-b13c-40f0-be93-1ff7ad3e36f0', + question: 'What the fick?', + type: 'Open question', + points: 0, + orderIndex: 1, + }, + }) + question: Question; + + @ApiProperty({ + description: 'Question option text', + type: String, + example: 'A. Rock', + }) + optionText: string; + + @ApiProperty({ + description: 'Is this question option correct?', + type: Boolean, + example: false, + }) + isCorrect: boolean; + + @ApiProperty({ + description: 'ehy this question option correct or incorrect?', + type: String, + example: 'Rock is not biology.', + }) + explanation: string; + + @ApiProperty({ + description: 'Exam created date', + type: Date, + example: new Date(), + }) + createdAt: Date; + + @ApiProperty({ + description: 'Exam updated date', + type: Date, + example: new Date(), + }) + updatedAt: Date; + + constructor(questionOption: QuestionOption) { + this.id = questionOption.id; + this.question = questionOption.question; + this.optionText = questionOption.optionText; + this.isCorrect = questionOption.isCorrect; + this.explanation = questionOption.explanation; + this.createdAt = questionOption.createdAt; + this.updatedAt = questionOption.updatedAt; + } +} + +export class PaginatedQuestionOptionResponseDto extends PaginatedResponse( + QuestionOptionResponseDto, +) { + constructor( + questionOption: QuestionOption[], + total: number, + pageSize: number, + currentPage: number, + ) { + const questionOptionDtos = questionOption.map( + (questionOption) => new QuestionOptionResponseDto(questionOption), + ); + super(questionOptionDtos, total, pageSize, currentPage); + } +} diff --git a/src/question-option/dtos/update-question-option.dto.ts b/src/question-option/dtos/update-question-option.dto.ts index e69de29..f349488 100644 --- a/src/question-option/dtos/update-question-option.dto.ts +++ b/src/question-option/dtos/update-question-option.dto.ts @@ -0,0 +1,6 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateQuestionOptionDto } from './create-question-option.dto'; + +export class UpdateQuestionOptionDto extends PartialType( + CreateQuestionOptionDto, +) {} diff --git a/src/question-option/question-option.controller.ts b/src/question-option/question-option.controller.ts index e69de29..a00b0fd 100644 --- a/src/question-option/question-option.controller.ts +++ b/src/question-option/question-option.controller.ts @@ -0,0 +1,211 @@ +import { + Body, + Controller, + Delete, + Get, + HttpCode, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, +} from '@nestjs/common'; +import { ApiTags, ApiBearerAuth, ApiResponse, ApiQuery } from '@nestjs/swagger'; +import { QuestionOptionService } from './question-option.service'; +import { + PaginatedQuestionOptionResponseDto, + QuestionOptionResponseDto, +} from './dtos/question-option-response.dto'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums'; +import { CreateQuestionOptionDto } from './dtos/create-question-option.dto'; +import { UpdateQuestionOptionDto } from './dtos/update-question-option.dto'; + +@Controller('questionOption') +@Injectable() +@ApiTags('QuestionOption') +@ApiBearerAuth() +export class QuestionOptionController { + constructor(private readonly questionOptionService: QuestionOptionService) {} + + @Get() + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all question options', + type: PaginatedQuestionOptionResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findAll( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return await this.questionOptionService.findAll(request, { + page: query.page, + limit: query.limit, + search: query.search, + }); + } + + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns a question option', + type: QuestionOptionResponseDto, + }) + async findOne( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const questionOption = await this.questionOptionService.findOne(request, { + where: { id }, + }); + return new QuestionOptionResponseDto(questionOption); + } + + @Get('question/:questionId') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all question options in question', + type: PaginatedQuestionOptionResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findQuestionByExamId( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + @Param( + 'questionId', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + questionId: string, + ): Promise { + return await this.questionOptionService.findQuestionOptionByQuestionId( + request, + questionId, + { + page: query.page, + limit: query.limit, + search: query.search, + }, + ); + } + + @Post() + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Create an question option', + type: QuestionOptionResponseDto, + }) + @HttpCode(HttpStatus.CREATED) + async createQuestionOption( + @Body() createQuestionOptionDto: CreateQuestionOptionDto, + ): Promise { + const questionOption = + await this.questionOptionService.createQuestionOption( + createQuestionOptionDto, + ); + return new QuestionOptionResponseDto(questionOption); + } + + @Patch(':id') + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Update a question option', + type: QuestionOptionResponseDto, + }) + async updateQuestionOption( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @Body() updateQuestionOptionDto: UpdateQuestionOptionDto, + ): Promise { + const questionOption = + await this.questionOptionService.updateQuestionOption( + request, + id, + updateQuestionOptionDto, + ); + return new QuestionOptionResponseDto(questionOption); + } + + @Delete(':id') + @Roles(Role.TEACHER) + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Delete a question option', + }) + @HttpCode(HttpStatus.NO_CONTENT) + async deleteExam( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise<{ massage: string }> { + await this.questionOptionService.deleteQuestionOption(request, id); + return { massage: 'Question option deleted successfully' }; + } +} diff --git a/src/question-option/question-option.entity.ts b/src/question-option/question-option.entity.ts index e69de29..3601daa 100644 --- a/src/question-option/question-option.entity.ts +++ b/src/question-option/question-option.entity.ts @@ -0,0 +1,51 @@ +import { Question } from 'src/question/question.entity'; +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +@Entity() +export class QuestionOption { + @PrimaryGeneratedColumn('uuid') + id: string; + + @ManyToOne(() => Question, (question) => question.questionOption, { + onDelete: 'CASCADE', + nullable: false, + }) + @JoinColumn({ name: 'question_id' }) + question: Question; + + @Column({ name: 'question_id' }) + questionId: string; + + @Column({ + nullable: false, + }) + optionText: string; + + @Column({ + nullable: false, + }) + isCorrect: boolean; + + @Column({ + nullable: false, + }) + explanation: string; + + @CreateDateColumn({ + type: 'timestamp with time zone', + }) + createdAt: Date; + + @UpdateDateColumn({ + type: 'timestamp with time zone', + }) + updatedAt: Date; +} diff --git a/src/question-option/question-option.module.ts b/src/question-option/question-option.module.ts index e69de29..e2b0422 100644 --- a/src/question-option/question-option.module.ts +++ b/src/question-option/question-option.module.ts @@ -0,0 +1,19 @@ +import { TypeOrmModule } from '@nestjs/typeorm'; +import { DatabaseModule } from 'src/database/database.module'; +import { QuestionOption } from './question-option.entity'; +import { Module } from '@nestjs/common'; +import { QuestionOptionController } from './question-option.controller'; +import { questionOptionProviders } from './question-option.providers'; +import { QuestionOptionService } from './question-option.service'; +import { Question } from 'src/question/question.entity'; + +@Module({ + imports: [ + DatabaseModule, + TypeOrmModule.forFeature([QuestionOption, Question]), + ], + controllers: [QuestionOptionController], + providers: [...questionOptionProviders, QuestionOptionService], + exports: [QuestionOptionService], +}) +export class QuestionOptionModule {} diff --git a/src/question-option/question-option.providers.ts b/src/question-option/question-option.providers.ts index e69de29..6c445b8 100644 --- a/src/question-option/question-option.providers.ts +++ b/src/question-option/question-option.providers.ts @@ -0,0 +1,11 @@ +import { DataSource } from 'typeorm'; +import { QuestionOption } from './question-option.entity'; + +export const questionOptionProviders = [ + { + provide: 'QuestionOptionRepository', + useFactory: (dataSource: DataSource) => + dataSource.getRepository(QuestionOption), + inject: ['DataSource'], + }, +]; diff --git a/src/question-option/question-option.service.ts b/src/question-option/question-option.service.ts index e69de29..5fb2993 100644 --- a/src/question-option/question-option.service.ts +++ b/src/question-option/question-option.service.ts @@ -0,0 +1,243 @@ +import { + Injectable, + Inject, + NotFoundException, + BadRequestException, +} from '@nestjs/common'; +import { + FindOneOptions, + FindOptionsSelect, + FindOptionsWhere, + ILike, + Repository, +} from 'typeorm'; +import { QuestionOption } from './question-option.entity'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { PaginatedQuestionOptionResponseDto } from './dtos/question-option-response.dto'; +import { createPagination } from 'src/shared/pagination'; +import { ExamStatus, Role } from 'src/shared/enums'; +import { CreateQuestionOptionDto } from './dtos/create-question-option.dto'; +import { Question } from 'src/question/question.entity'; +import { UpdateQuestionOptionDto } from './dtos/update-question-option.dto'; +@Injectable() +export class QuestionOptionService { + constructor( + @Inject('QuestionOptionRepository') + private readonly questionOptionRepository: Repository, + @Inject('QuestionRepository') + private readonly questionRepository: Repository, + ) {} + + async findAll( + request: AuthenticatedRequest, + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.questionOptionRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateCondition(request, search); + const question = await find({ + where: whereCondition, + relations: ['question'], + select: { + question: this.selectPopulateQuestion(), + }, + }).run(); + + return question; + } + + async findOne( + request: AuthenticatedRequest, + options: FindOneOptions = {}, + ): Promise { + const whereCondition = this.validateAndCreateCondition(request, ''); + + const where = Array.isArray(whereCondition) + ? [ + { ...whereCondition[0], ...options.where }, + { ...whereCondition[1], ...options.where }, + ] + : { ...whereCondition, ...options.where }; + + const question = await this.questionOptionRepository.findOne({ + ...options, + where, + relations: ['question'], + select: { + question: this.selectPopulateQuestion(), + }, + }); + + if (!question) { + throw new NotFoundException('Question not found'); + } + + return question; + } + + async findQuestionOptionByQuestionId( + request: AuthenticatedRequest, + questionId: string, + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.questionOptionRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateCondition(request, search); + whereCondition['question'] = { id: questionId }; + + const question = await this.questionRepository.findOne({ + where: { id: questionId }, + }); + + if (!question) { + throw new NotFoundException('Question not found.'); + } + + const questionOption = await find({ + where: whereCondition, + relations: ['question'], + select: { + question: this.selectPopulateQuestion(), + }, + }).run(); + return questionOption; + } + + private validateAndCreateCondition( + request: AuthenticatedRequest, + search: string, + ): FindOptionsWhere { + const baseSearch = search ? { optionText: ILike(`%${search}%`) } : {}; + + if (request.user.role === Role.STUDENT) { + return { + ...baseSearch, + question: { + exam: { + status: ExamStatus.PUBLISHED, + }, + }, + }; + } + + if (request.user.role === Role.TEACHER) { + return { + ...baseSearch, + question: { + exam: [ + { + status: ExamStatus.PUBLISHED, + }, + { + courseModule: { + course: { + teacher: { id: request.user.id }, + }, + }, + }, + ], + }, + }; + } + + if (request.user.role === Role.ADMIN) { + return { ...baseSearch }; + } + + return { + ...baseSearch, + question: { + exam: { + status: ExamStatus.PUBLISHED, + }, + }, + }; + } + + async createQuestionOption( + createQuestionOptionDto: CreateQuestionOptionDto, + ): Promise { + const question = await this.questionRepository.findOne({ + where: { id: createQuestionOptionDto.questionId }, + select: this.selectPopulateQuestion(), + }); + if (!question) { + throw new NotFoundException('Question option not found.'); + } + const questionOption = this.questionOptionRepository.create({ + ...createQuestionOptionDto, + question, + }); + if (!questionOption) { + throw new BadRequestException('Question option not create.'); + } + await this.questionOptionRepository.save(questionOption); + return questionOption; + } + + async updateQuestionOption( + request: AuthenticatedRequest, + id: string, + updateQuestionOptionDto: UpdateQuestionOptionDto, + ): Promise { + await this.findOne(request, { where: { id } }); + const questionOption = await this.questionOptionRepository.update( + id, + updateQuestionOptionDto, + ); + if (!questionOption) + throw new NotFoundException("Can't update question option"); + return await this.questionOptionRepository.findOne({ + where: { id }, + relations: ['question'], + select: { + question: this.selectPopulateQuestion(), + }, + }); + } + + async deleteQuestionOption( + request: AuthenticatedRequest, + id: string, + ): Promise { + try { + await this.findOne(request, { where: { id } }); + await this.questionOptionRepository.delete(id); + } catch (error) { + if (error instanceof Error) + throw new NotFoundException('Question option not found'); + } + } + + private selectPopulateQuestion(): FindOptionsSelect { + return { + id: true, + question: true, + type: true, + points: true, + orderIndex: true, + }; + } +} diff --git a/src/question/question.entity.ts b/src/question/question.entity.ts index 413ae08..9b31756 100644 --- a/src/question/question.entity.ts +++ b/src/question/question.entity.ts @@ -1,4 +1,5 @@ import { Exam } from 'src/exam/exam.entity'; +import { QuestionOption } from 'src/question-option/question-option.entity'; import { QuestionType } from 'src/shared/enums'; import { Entity, @@ -9,6 +10,7 @@ import { ManyToOne, JoinColumn, Unique, + OneToMany, } from 'typeorm'; @Entity() @Unique(['examId', 'orderIndex']) @@ -26,6 +28,15 @@ export class Question { @Column({ name: 'exam_id' }) examId: string; + @OneToMany( + () => QuestionOption, + (questionOption) => questionOption.question, + { + cascade: true, + }, + ) + questionOption: QuestionOption[]; + @Column({ nullable: false, }) diff --git a/src/question/question.service.ts b/src/question/question.service.ts index 35f59a3..e567def 100644 --- a/src/question/question.service.ts +++ b/src/question/question.service.ts @@ -67,9 +67,16 @@ export class QuestionService { ): Promise { const whereCondition = this.validateAndCreateCondition(request, ''); + const where = Array.isArray(whereCondition) + ? [ + { ...whereCondition[0], ...options.where }, + { ...whereCondition[1], ...options.where }, + ] + : { ...whereCondition, ...options.where }; + const question = await this.questionRepository.findOne({ ...options, - where: whereCondition, + where, relations: ['exam'], select: { exam: this.selectPopulateExam(), @@ -289,19 +296,6 @@ export class QuestionService { } } - private selectPopulateExamForQuery(): string[] { - return [ - 'question.id', - 'question.title', - 'question.description', - 'question.timeLimit', - 'question.passingScore', - 'question.maxAttempts', - 'question.shuffleQuestions', - 'question.status', - ]; - } - private selectPopulateExam(): FindOptionsSelect { return { id: true, diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index 436a67f..efea244 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -12,6 +12,7 @@ import { GLOBAL_CONFIG } from '../constants/global-config.constant'; import { Exam } from 'src/exam/exam.entity'; import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; import { Question } from 'src/question/question.entity'; +import { QuestionOption } from 'src/question-option/question-option.entity'; const configService = new ConfigService(); @@ -35,6 +36,7 @@ export const databaseConfig: DataSourceOptions = { ExamAttempt, Question, + QuestionOption, ], }; From 0534b7d0f353202611e9e0eaf041109f999b8731 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sun, 17 Nov 2024 14:55:59 +0700 Subject: [PATCH 060/155] feat: create exam-answer --- src/app.module.ts | 2 + .../dtos/create-exam-answer.dto.ts | 54 +++++++++++ .../dtos/exam-answer-response.dto.ts | 95 +++++++++++++++++++ .../dtos/update-exam-answer.dto.ts | 4 + src/exam-answer/exam-answer.controller.ts | 11 +++ src/exam-answer/exam-answer.entity.ts | 79 +++++++++++++++ src/exam-answer/exam-answer.module.ts | 15 +++ src/exam-answer/exam-answer.providers.ts | 11 +++ src/exam-answer/exam-answer.service.ts | 83 ++++++++++++++++ src/exam-attempt/exam-attempt.entity.ts | 7 ++ src/exam/exam.entity.ts | 3 +- src/question-option/question-option.entity.ts | 7 ++ src/question/question.entity.ts | 6 ++ src/shared/configs/database.config.ts | 2 + 14 files changed, 378 insertions(+), 1 deletion(-) create mode 100644 src/exam-answer/dtos/create-exam-answer.dto.ts create mode 100644 src/exam-answer/dtos/exam-answer-response.dto.ts create mode 100644 src/exam-answer/dtos/update-exam-answer.dto.ts create mode 100644 src/exam-answer/exam-answer.controller.ts create mode 100644 src/exam-answer/exam-answer.entity.ts create mode 100644 src/exam-answer/exam-answer.module.ts create mode 100644 src/exam-answer/exam-answer.providers.ts create mode 100644 src/exam-answer/exam-answer.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index 86595af..cf932ae 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -26,6 +26,7 @@ import { UserModule } from './user/user.module'; import { ExamAttemptModule } from './exam-attempt/exam-attempt.module'; import { QuestionModule } from './question/question.module'; import { QuestionOptionModule } from './question-option/question-option.module'; +import { ExamAnswer } from './exam-answer/exam-answer.entity'; const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); @@ -65,6 +66,7 @@ const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); ExamAttemptModule, QuestionModule, QuestionOptionModule, + ExamAnswer, ], controllers: [AppController], providers: [ diff --git a/src/exam-answer/dtos/create-exam-answer.dto.ts b/src/exam-answer/dtos/create-exam-answer.dto.ts new file mode 100644 index 0000000..93ccf33 --- /dev/null +++ b/src/exam-answer/dtos/create-exam-answer.dto.ts @@ -0,0 +1,54 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsInt, IsNotEmpty } from 'class-validator'; + +export class CreateExamAnswerDto { + @IsNotEmpty() + @ApiProperty({ + description: 'Exam Attempt ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + examAttemptId: string; + + @IsNotEmpty() + @ApiProperty({ + description: 'Question ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + questionId: string; + + @IsNotEmpty() + @ApiProperty({ + description: 'Select Question ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + selectedOptionId: string; + + @IsNotEmpty() + @ApiProperty({ + description: 'Answer text', + type: String, + example: 'biology', + }) + answerText: string; + + @IsNotEmpty() + @IsBoolean() + @ApiProperty({ + description: 'Is answer correct?', + type: Boolean, + example: false, + }) + isCorrect: boolean; + + @IsNotEmpty() + @IsInt() + @ApiProperty({ + description: 'Points in this answer', + type: Number, + example: 0, + }) + points: number = 0; +} diff --git a/src/exam-answer/dtos/exam-answer-response.dto.ts b/src/exam-answer/dtos/exam-answer-response.dto.ts new file mode 100644 index 0000000..a840569 --- /dev/null +++ b/src/exam-answer/dtos/exam-answer-response.dto.ts @@ -0,0 +1,95 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { ExamAnswer } from '../exam-answer.entity'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; +import { Question } from 'src/question/question.entity'; +import { QuestionOption } from 'src/question-option/question-option.entity'; + +export class ExamAnswerResponseDto { + @ApiProperty({ + description: 'Exam Answer ID', + type: String, + example: '123e4567-e89b-12d3-a456-426614174000', + }) + id: string; + + @ApiProperty({ + description: 'Exam Attempt Data', + type: ExamAttempt, + example: { id: '123e4567-e89b-12d3-a456-426614174000' }, + }) + examAttempt: ExamAttempt; + + @ApiProperty({ + description: 'Question Data', + type: Question, + example: { id: '123e4567-e89b-12d3-a456-426614174000' }, + }) + question: Question; + + @ApiProperty({ + description: 'Select Question Data', + type: QuestionOption, + example: { id: '123e4567-e89b-12d3-a456-426614174000' }, + }) + selectedOption: QuestionOption; + + @ApiProperty({ + description: 'Answer text', + type: String, + example: 'biology', + }) + answerText: string; + + @ApiProperty({ + description: 'Is answer correct?', + type: Boolean, + example: false, + }) + isCorrect: boolean; + + @ApiProperty({ + description: 'Points in this answer', + type: Number, + example: 0, + }) + points: number = 0; + + @ApiProperty({ + description: 'Exam created date', + type: Date, + example: new Date(), + }) + createdAt: Date; + + @ApiProperty({ + description: 'Exam updated date', + type: Date, + example: new Date(), + }) + updatedAt: Date; + + constructor(examAnswer: ExamAnswer) { + this.id = examAnswer.id; + this.isCorrect = examAnswer.isCorrect; + this.points = examAnswer.points; + this.createdAt = examAnswer.createdAt; + this.updatedAt = examAnswer.updatedAt; + } +} + +export class PaginatedExamAnswerResponseDto extends PaginatedResponse( + ExamAnswerResponseDto, +) { + constructor( + examAnswer: ExamAnswer[], + total: number, + pageSize: number, + currentPage: number, + ) { + const examAnswerDtos = examAnswer.map( + (examAnswer) => new ExamAnswerResponseDto(examAnswer), + ); + super(examAnswerDtos, total, pageSize, currentPage); + } +} diff --git a/src/exam-answer/dtos/update-exam-answer.dto.ts b/src/exam-answer/dtos/update-exam-answer.dto.ts new file mode 100644 index 0000000..7ae39b6 --- /dev/null +++ b/src/exam-answer/dtos/update-exam-answer.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateExamAnswerDto } from './create-exam-answer.dto'; + +export class UpdateExamAnswerDto extends PartialType(CreateExamAnswerDto) {} diff --git a/src/exam-answer/exam-answer.controller.ts b/src/exam-answer/exam-answer.controller.ts new file mode 100644 index 0000000..202d1a3 --- /dev/null +++ b/src/exam-answer/exam-answer.controller.ts @@ -0,0 +1,11 @@ +import { Controller, Injectable } from '@nestjs/common'; +import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { ExamAnswerService } from './exam-answer.service'; + +@Controller('exam') +@ApiTags('Exam') +@ApiBearerAuth() +@Injectable() +export class ExamController { + constructor(private readonly examAnswerService: ExamAnswerService) {} +} diff --git a/src/exam-answer/exam-answer.entity.ts b/src/exam-answer/exam-answer.entity.ts new file mode 100644 index 0000000..8440f25 --- /dev/null +++ b/src/exam-answer/exam-answer.entity.ts @@ -0,0 +1,79 @@ +import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; +import { QuestionOption } from 'src/question-option/question-option.entity'; +import { Question } from 'src/question/question.entity'; +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +@Entity() +export class ExamAnswer { + @PrimaryGeneratedColumn('uuid') + id: string; + + @ManyToOne(() => ExamAttempt, (examAttempt) => examAttempt.examAnswer, { + onDelete: 'CASCADE', + nullable: false, + }) + @JoinColumn({ name: 'exam_attempt_id' }) + examAttempt: ExamAttempt; + + @Column({ name: 'exam_attempt_id' }) + examAttemptId: string; + + @ManyToOne(() => Question, (question) => question.examAnswer, { + onDelete: 'CASCADE', + nullable: false, + }) + @JoinColumn({ name: 'question_id' }) + question: Question; + + @Column({ name: 'question_id' }) + questionId: string; + + @ManyToOne( + () => QuestionOption, + (questionOption) => questionOption.examAnswer, + { + onDelete: 'CASCADE', + nullable: false, + }, + ) + @JoinColumn({ name: 'question_option_id' }) + selectedOption: QuestionOption; + + @Column({ name: 'question_option_id' }) + selectedOptionId: string; + + @Column({ + nullable: false, + }) + answerText: string; + + @Column({ + nullable: true, + type: Boolean, + }) + isCorrect: boolean; + + @Column({ + nullable: false, + default: 0, + }) + points: number; + + @CreateDateColumn({ + type: 'timestamp with time zone', + }) + createdAt: Date; + + @UpdateDateColumn({ + type: 'timestamp with time zone', + }) + updatedAt: Date; +} diff --git a/src/exam-answer/exam-answer.module.ts b/src/exam-answer/exam-answer.module.ts new file mode 100644 index 0000000..0b4290c --- /dev/null +++ b/src/exam-answer/exam-answer.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { DatabaseModule } from 'src/database/database.module'; +import { ExamAnswer } from './exam-answer.entity'; +import { ExamAttemptController } from 'src/exam-attempt/exam-attempt.controller'; +import { examAnswerProviders } from './exam-answer.providers'; +import { ExamAnswerService } from './exam-answer.service'; + +@Module({ + imports: [DatabaseModule, TypeOrmModule.forFeature([ExamAnswer])], + controllers: [ExamAttemptController], + providers: [...examAnswerProviders, ExamAnswerService], + exports: [ExamAnswerService], +}) +export class ExamAnswerModule {} diff --git a/src/exam-answer/exam-answer.providers.ts b/src/exam-answer/exam-answer.providers.ts new file mode 100644 index 0000000..a786078 --- /dev/null +++ b/src/exam-answer/exam-answer.providers.ts @@ -0,0 +1,11 @@ +import { DataSource } from 'typeorm'; +import { ExamAnswer } from './exam-answer.entity'; + +export const examAnswerProviders = [ + { + provide: 'ExamAnswerRepository', + useFactory: (dataSource: DataSource) => + dataSource.getRepository(ExamAnswer), + inject: ['DataSource'], + }, +]; diff --git a/src/exam-answer/exam-answer.service.ts b/src/exam-answer/exam-answer.service.ts new file mode 100644 index 0000000..b47a8ab --- /dev/null +++ b/src/exam-answer/exam-answer.service.ts @@ -0,0 +1,83 @@ +import { Injectable, Inject } from '@nestjs/common'; +import { FindOptionsWhere, ILike, Repository } from 'typeorm'; +import { ExamAnswer } from './exam-answer.entity'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { PaginatedExamAnswerResponseDto } from './dtos/exam-answer-response.dto'; +import { createPagination } from 'src/shared/pagination'; +import { ExamStatus, Role } from 'src/shared/enums'; + +@Injectable() +export class ExamAnswerService { + constructor( + @Inject('ExamAnswerRepository') + private readonly examAnswerRepository: Repository, + ) {} + + // async findAll( + // request: AuthenticatedRequest, + // { + // page = 1, + // limit = 20, + // search = '', + // }: { + // page?: number; + // limit?: number; + // search?: string; + // }, + // ): Promise { + // const { find } = await createPagination(this.examAnswerRepository, { + // page, + // limit, + // }); + + // const whereCondition = this.validateAndCreateCondition(request, search); + // const exam = await find({ + // where: whereCondition, + // }).run(); + + // return exam; + // } + + // private validateAndCreateCondition( + // request: AuthenticatedRequest, + // search: string, + // ): FindOptionsWhere | FindOptionsWhere[] { + // const baseSearch = search ? { answerText: ILike(`%${search}%`) } : {}; + + // if (request.user.role === Role.STUDENT) { + // return { + // ...baseSearch, + // question: { exam: { status: ExamStatus.PUBLISHED } }, + // }; + // } + + // if (request.user.role === Role.TEACHER) { + // return [ + // { + // ...baseSearch, + // question: { + // exam: { + // courseModule: { + // course: { + // teacher: { + // id: request.user.id, + // }, + // }, + // }, + // }, + // }, + // }, + // { + // ...baseSearch, + // question: { exam: { status: ExamStatus.PUBLISHED } }, + // }, + // ]; + // } + + // if (request.user.role === Role.ADMIN) { + // return { ...baseSearch }; + // } + + // return { ...baseSearch, status: ExamStatus.PUBLISHED }; + // } +} diff --git a/src/exam-attempt/exam-attempt.entity.ts b/src/exam-attempt/exam-attempt.entity.ts index 01f4435..b5b45b6 100644 --- a/src/exam-attempt/exam-attempt.entity.ts +++ b/src/exam-attempt/exam-attempt.entity.ts @@ -1,3 +1,4 @@ +import { ExamAnswer } from 'src/exam-answer/exam-answer.entity'; import { Exam } from 'src/exam/exam.entity'; import { ExamAttemptStatus } from 'src/shared/enums'; import { User } from 'src/user/user.entity'; @@ -7,6 +8,7 @@ import { Entity, JoinColumn, ManyToOne, + OneToMany, PrimaryGeneratedColumn, Unique, UpdateDateColumn, @@ -37,6 +39,11 @@ export class ExamAttempt { @Column({ name: 'user_id' }) userId: String; + @OneToMany(() => ExamAnswer, (examAnswer) => examAnswer.examAttempt, { + cascade: true, + }) + examAnswer: ExamAnswer[]; + @Column({ nullable: false, default: 0, diff --git a/src/exam/exam.entity.ts b/src/exam/exam.entity.ts index af288f1..daa3d72 100644 --- a/src/exam/exam.entity.ts +++ b/src/exam/exam.entity.ts @@ -31,7 +31,8 @@ export class Exam { @OneToMany(() => ExamAttempt, (examAttempt) => examAttempt.exam, { cascade: true, }) - examAttempt: ExamAttempt; + examAttempt: ExamAttempt[]; + @OneToMany(() => Question, (question) => question.exam, { cascade: true, }) diff --git a/src/question-option/question-option.entity.ts b/src/question-option/question-option.entity.ts index 3601daa..3bd9f54 100644 --- a/src/question-option/question-option.entity.ts +++ b/src/question-option/question-option.entity.ts @@ -1,3 +1,4 @@ +import { ExamAnswer } from 'src/exam-answer/exam-answer.entity'; import { Question } from 'src/question/question.entity'; import { Column, @@ -5,6 +6,7 @@ import { Entity, JoinColumn, ManyToOne, + OneToMany, PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; @@ -24,6 +26,11 @@ export class QuestionOption { @Column({ name: 'question_id' }) questionId: string; + @OneToMany(() => ExamAnswer, (examAnswer) => examAnswer.selectedOption, { + cascade: true, + }) + examAnswer: ExamAnswer[]; + @Column({ nullable: false, }) diff --git a/src/question/question.entity.ts b/src/question/question.entity.ts index 9b31756..b7cdbb1 100644 --- a/src/question/question.entity.ts +++ b/src/question/question.entity.ts @@ -1,3 +1,4 @@ +import { ExamAnswer } from 'src/exam-answer/exam-answer.entity'; import { Exam } from 'src/exam/exam.entity'; import { QuestionOption } from 'src/question-option/question-option.entity'; import { QuestionType } from 'src/shared/enums'; @@ -37,6 +38,11 @@ export class Question { ) questionOption: QuestionOption[]; + @OneToMany(() => ExamAnswer, (examAnswer) => examAnswer.question, { + cascade: true, + }) + examAnswer: ExamAnswer[]; + @Column({ nullable: false, }) diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index efea244..964e541 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -13,6 +13,7 @@ import { Exam } from 'src/exam/exam.entity'; import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; import { Question } from 'src/question/question.entity'; import { QuestionOption } from 'src/question-option/question-option.entity'; +import { ExamAnswer } from 'src/exam-answer/exam-answer.entity'; const configService = new ConfigService(); @@ -37,6 +38,7 @@ export const databaseConfig: DataSourceOptions = { ExamAttempt, Question, QuestionOption, + ExamAnswer, ], }; From 9deb537cd44b50b52a1cdb0b899cce18dc6f5d8b Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sun, 17 Nov 2024 15:26:30 +0700 Subject: [PATCH 061/155] feat: student can create and update exam-attempt --- .../dtos/create-exam-attempt.dto.ts | 8 ---- src/exam-attempt/exam-attempt.controller.ts | 14 ++++--- src/exam-attempt/exam-attempt.service.dto.ts | 39 ++++++------------- 3 files changed, 20 insertions(+), 41 deletions(-) diff --git a/src/exam-attempt/dtos/create-exam-attempt.dto.ts b/src/exam-attempt/dtos/create-exam-attempt.dto.ts index 172a336..a8100bc 100644 --- a/src/exam-attempt/dtos/create-exam-attempt.dto.ts +++ b/src/exam-attempt/dtos/create-exam-attempt.dto.ts @@ -11,14 +11,6 @@ export class CreateExamAttemptDto { }) examId: string; - @IsNotEmpty() - @ApiProperty({ - description: 'User ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }) - userId: string; - @IsNotEmpty() @Min(0) @IsInt() diff --git a/src/exam-attempt/exam-attempt.controller.ts b/src/exam-attempt/exam-attempt.controller.ts index 5eb9caa..08af870 100644 --- a/src/exam-attempt/exam-attempt.controller.ts +++ b/src/exam-attempt/exam-attempt.controller.ts @@ -93,7 +93,7 @@ export class ExamAttemptController { } @Post() - @Roles(Role.TEACHER) + @Roles(Role.STUDENT) @ApiResponse({ status: HttpStatus.CREATED, description: 'Create an exam-attempt', @@ -101,15 +101,18 @@ export class ExamAttemptController { }) @HttpCode(HttpStatus.CREATED) async createExamAttempt( + @Req() request: AuthenticatedRequest, @Body() createExamAttemptDto: CreateExamAttemptDto, ): Promise { - const exam = - await this.examAttemptService.createExamAttempt(createExamAttemptDto); + const exam = await this.examAttemptService.createExamAttempt( + request, + createExamAttemptDto, + ); return new ExamAttemptResponseDto(exam); } @Patch(':id') - @Roles(Role.TEACHER) + @Roles(Role.STUDENT) @ApiResponse({ status: HttpStatus.OK, description: 'Update an exam-attempt', @@ -136,7 +139,8 @@ export class ExamAttemptController { } @Patch('/submit/:id') - @Roles(Role.TEACHER) + @Roles(Role.STUDENT) + @Roles(Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, description: 'Update an exam-attempt', diff --git a/src/exam-attempt/exam-attempt.service.dto.ts b/src/exam-attempt/exam-attempt.service.dto.ts index 6e0db6f..e44c8b3 100644 --- a/src/exam-attempt/exam-attempt.service.dto.ts +++ b/src/exam-attempt/exam-attempt.service.dto.ts @@ -75,26 +75,13 @@ export class ExamAttemptService { } if (request.user.role === Role.STUDENT) { - return [ - { - ...baseSearch, - submittedAt: Not(IsNull()), - status: ExamAttemptStatus.FAILED, - userId: request.user.id, - exam: { - status: ExamStatus.PUBLISHED, - }, - }, - { - ...baseSearch, - submittedAt: Not(IsNull()), - status: ExamAttemptStatus.PASSED, - userId: request.user.id, - exam: { - status: ExamStatus.PUBLISHED, - }, + return { + ...baseSearch, + userId: request.user.id, + exam: { + status: ExamStatus.PUBLISHED, }, - ]; + }; } if (request.user.role === Role.TEACHER) { @@ -114,14 +101,9 @@ export class ExamAttemptService { return { ...baseSearch, + userId: request.user.id, exam: { - courseModule: { - course: { - teacher: { - id: request.user.id, - }, - }, - }, + status: ExamStatus.PUBLISHED, }, }; } @@ -157,6 +139,7 @@ export class ExamAttemptService { } async createExamAttempt( + request: AuthenticatedRequest, createExamAttemptDto: CreateExamAttemptDto, ): Promise { const exam = await this.examRepository.findOne({ @@ -167,7 +150,7 @@ export class ExamAttemptService { throw new NotFoundException('Exam not found.'); } const user = await this.userRepository.findOne({ - where: { id: createExamAttemptDto.userId }, + where: { id: request.user.id }, select: this.selectPopulateUser(), }); if (!user) { @@ -178,7 +161,7 @@ export class ExamAttemptService { if ( (await this.countExamAttempts( createExamAttemptDto.examId, - createExamAttemptDto.userId, + request.user.id, )) >= exam.maxAttempts ) throw new ForbiddenException( From c96005b93a3f02b243441deb85d6b3288c805031 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sun, 17 Nov 2024 15:28:20 +0700 Subject: [PATCH 062/155] feat: delete admin permission in update --- src/exam-attempt/exam-attempt.controller.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/exam-attempt/exam-attempt.controller.ts b/src/exam-attempt/exam-attempt.controller.ts index 08af870..1315192 100644 --- a/src/exam-attempt/exam-attempt.controller.ts +++ b/src/exam-attempt/exam-attempt.controller.ts @@ -140,7 +140,6 @@ export class ExamAttemptController { @Patch('/submit/:id') @Roles(Role.STUDENT) - @Roles(Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, description: 'Update an exam-attempt', From a46a4fe44bebeb85a50b3415301a6fdfb2e23c41 Mon Sep 17 00:00:00 2001 From: Potsawee Date: Sun, 17 Nov 2024 15:50:44 +0700 Subject: [PATCH 063/155] add error handler in reward --- src/reward/reward.entity.ts | 2 +- src/reward/reward.service.ts | 4 + src/user/user.entity.ts | 2 +- .../dtos/update-status-user-reward.dto.ts | 5 +- .../dtos/user-reward-response.dto.ts | 8 +- src/userReward/user-reward.entity.ts | 12 ++- src/userReward/user-reward.service.ts | 102 +++++++++--------- 7 files changed, 73 insertions(+), 62 deletions(-) diff --git a/src/reward/reward.entity.ts b/src/reward/reward.entity.ts index 5f21f33..28adaab 100644 --- a/src/reward/reward.entity.ts +++ b/src/reward/reward.entity.ts @@ -52,7 +52,7 @@ export class Reward { }) status: Status; - @OneToMany(() => UserReward, (userReward) => userReward.rewardId) + @OneToMany(() => UserReward, (userReward) => userReward.reward) userReward: UserReward; @CreateDateColumn({ diff --git a/src/reward/reward.service.ts b/src/reward/reward.service.ts index 57af163..46eb39a 100644 --- a/src/reward/reward.service.ts +++ b/src/reward/reward.service.ts @@ -28,6 +28,8 @@ export class RewardService { async create(CreateRewardDto: CreateRewardDto): Promise { try { + if (CreateRewardDto.points < 0) + throw new BadRequestException('points should not be less than zero'); return this.rewardRepository.save(CreateRewardDto); } catch (error) { if (error instanceof Error) { @@ -38,6 +40,8 @@ export class RewardService { async update(id: string, UpdateRewardDto: UpdateRewardDto): Promise { try { + if (UpdateRewardDto.points < 0) + throw new BadRequestException('points should not be less than zero'); await this.rewardRepository.update(id, UpdateRewardDto); return this.rewardRepository.findOne({ where: { id } }); } catch (error) { diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index c3ebeb2..5ff37bd 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -54,7 +54,7 @@ export class User { @OneToMany(() => Course, (course) => course.teacher) courses: Course[]; - @OneToMany(() => UserReward, (userReward) => userReward.userId) + @OneToMany(() => UserReward, (userReward) => userReward.user) rewards: UserReward[]; @CreateDateColumn({ diff --git a/src/userReward/dtos/update-status-user-reward.dto.ts b/src/userReward/dtos/update-status-user-reward.dto.ts index cb9a803..c53799b 100644 --- a/src/userReward/dtos/update-status-user-reward.dto.ts +++ b/src/userReward/dtos/update-status-user-reward.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { Status } from '../enums/status.enum'; -import { IsNotEmpty } from 'class-validator'; +import { IsEnum, IsNotEmpty } from 'class-validator'; export class UpdateStatusUserReward { @ApiProperty({ @@ -10,5 +10,8 @@ export class UpdateStatusUserReward { enum: Status, }) @IsNotEmpty() + @IsEnum(Status, { + message: `status should be either ${Status.DELIVERED} ${Status.EXPIRED} or ${Status.PENDING}`, + }) status: Status; } diff --git a/src/userReward/dtos/user-reward-response.dto.ts b/src/userReward/dtos/user-reward-response.dto.ts index 456baf6..9ac2688 100644 --- a/src/userReward/dtos/user-reward-response.dto.ts +++ b/src/userReward/dtos/user-reward-response.dto.ts @@ -15,14 +15,14 @@ export class UserRewardResponseDto { type: Object, example: { id: '4a3522e7-3080-46a6-83ff-778ce829e8ef' }, }) - userId: { id: string }; + user: { id: string }; @ApiProperty({ description: 'reward matched by id', type: Object, example: { id: '4a3522e7-3080-46a6-83ff-778ce829e8ef', name: 'reward' }, }) - rewardId: { id: string; name: string }; + reward: { id: string; name: string }; @ApiProperty({ description: 'how many points user spent on this reward', @@ -62,8 +62,8 @@ export class UserRewardResponseDto { constructor(userReward: UserReward) { this.id = userReward.id; - this.userId = userReward.userId; - this.rewardId = userReward.rewardId; + this.user = userReward.user; + this.reward = userReward.reward; this.pointsSpent = userReward.pointsSpent; this.status = userReward.status; this.redeemedAt = userReward.redeemedAt; diff --git a/src/userReward/user-reward.entity.ts b/src/userReward/user-reward.entity.ts index d65a2af..a6f2e2a 100644 --- a/src/userReward/user-reward.entity.ts +++ b/src/userReward/user-reward.entity.ts @@ -15,11 +15,15 @@ export class UserReward { @PrimaryGeneratedColumn('uuid') id: string; - @ManyToOne(() => User, (user) => user.rewards) - userId: User; + @ManyToOne(() => User, (user) => user.rewards, { + onDelete: 'CASCADE', + }) + user: User; - @ManyToOne(() => Reward, (reward) => reward.userReward) - rewardId: Reward; + @ManyToOne(() => Reward, (reward) => reward.userReward, { + onDelete: 'CASCADE', + }) + reward: Reward; @Column({ nullable: false, diff --git a/src/userReward/user-reward.service.ts b/src/userReward/user-reward.service.ts index 596131b..b769be3 100644 --- a/src/userReward/user-reward.service.ts +++ b/src/userReward/user-reward.service.ts @@ -4,13 +4,14 @@ import { Injectable, NotFoundException, } from '@nestjs/common'; -import { FindOneOptions, Repository } from 'typeorm'; +import { Repository } from 'typeorm'; import { UserReward } from './user-reward.entity'; import { User } from 'src/user/user.entity'; import { Reward } from 'src/reward/reward.entity'; import { Status } from 'src/reward/enums/status.enum'; import { Status as rewardStatus } from './enums/status.enum'; import { UpdateStatusUserReward } from './dtos/update-status-user-reward.dto'; +import { error } from 'console'; @Injectable() export class UserRewardService { @@ -44,8 +45,8 @@ export class UserRewardService { //decrease user points this.userRepository.update(userId, { points: user.points - reward.points }); const newUserReward = new UserReward(); - newUserReward.userId = user; - newUserReward.rewardId = reward; + newUserReward.user = user; + newUserReward.reward = reward; newUserReward.pointsSpent = reward.points; newUserReward.status = rewardStatus.PENDING; const userRewardRes = await this.userRewardRepository.save(newUserReward); @@ -54,16 +55,20 @@ export class UserRewardService { async findAll(): Promise { return this.userRewardRepository.find({ + relations: { + reward: true, + user: true, + }, select: { id: true, pointsSpent: true, status: true, createdAt: true, updatedAt: true, - userId: { + user: { id: true, }, - rewardId: { + reward: { id: true, name: true, }, @@ -74,8 +79,8 @@ export class UserRewardService { async findOne(id: string): Promise { const userReward = await this.userRewardRepository.findOne({ relations: { - userId: true, - rewardId: true, + user: true, + reward: true, }, where: { id }, select: { @@ -84,10 +89,10 @@ export class UserRewardService { status: true, createdAt: true, updatedAt: true, - userId: { + user: { id: true, }, - rewardId: { + reward: { id: true, name: true, }, @@ -98,39 +103,35 @@ export class UserRewardService { } async findByUser(id: string): Promise { - try { - const userRewards = await this.userRewardRepository.find({ - relations: { - userId: true, - rewardId: true, + const userRewards = await this.userRewardRepository.find({ + relations: { + user: true, + reward: true, + }, + where: { + user: { + id, }, - where: { - userId: { - id, - }, + }, + select: { + id: true, + pointsSpent: true, + status: true, + createdAt: true, + updatedAt: true, + user: { + id: true, }, - select: { + reward: { id: true, - pointsSpent: true, - status: true, - createdAt: true, - updatedAt: true, - userId: { - id: true, - }, - rewardId: { - id: true, - name: true, - }, + name: true, }, - }); - if (userRewards.length <= 0) { - throw new NotFoundException("user doesn't have reward yet"); - } - return userRewards; - } catch (error) { - if (error instanceof Error) throw new NotFoundException('user not exist'); + }, + }); + if (userRewards.length <= 0) { + throw new NotFoundException("user doesn't have reward yet"); } + return userRewards; } async updateStatus( @@ -138,16 +139,18 @@ export class UserRewardService { userReward: UpdateStatusUserReward, ): Promise { try { - await this.userRewardRepository.update(id, { status: userReward.status }); + const userRewardRes = await this.userRewardRepository.update(id, { + status: userReward.status, + }); + if (userRewardRes.affected == 0) + throw new NotFoundException('user reward not found'); return this.userRewardRepository.findOne({ relations: { - userId: true, - rewardId: true, + user: true, + reward: true, }, where: { - userId: { - id, - }, + id, }, select: { id: true, @@ -155,10 +158,10 @@ export class UserRewardService { status: true, createdAt: true, updatedAt: true, - userId: { + user: { id: true, }, - rewardId: { + reward: { id: true, name: true, }, @@ -171,11 +174,8 @@ export class UserRewardService { } async delete(id: string): Promise { - try { - await this.userRewardRepository.softDelete(id); - } catch (error) { - if (error instanceof Error) - throw new NotFoundException('user-reward not found'); - } + const deleteResult = await this.userRewardRepository.delete(id); + if (deleteResult.affected == 0) + throw new NotFoundException('user-reward not found'); } } From a188d926330f20f51c9b8aeff3132ec537d33edb Mon Sep 17 00:00:00 2001 From: khris-xp Date: Sun, 17 Nov 2024 16:14:32 +0700 Subject: [PATCH 064/155] feat: user occupation module service --- src/app.module.ts | 2 + src/progress/progress.service.ts | 1 - src/shared/configs/database.config.ts | 2 + .../dtos/create-user-occupation.dto.ts | 22 +++ .../dtos/update-user-occupation.dto.ts | 6 + .../dtos/user-occupation-response.dto.ts | 63 ++++++++ .../user-occupation.controller.ts | 145 ++++++++++++++++++ src/user-occupation/user-occupation.entity.ts | 39 +++++ src/user-occupation/user-occupation.module.ts | 12 ++ .../user-occupation.service.ts | 83 ++++++++++ 10 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 src/user-occupation/dtos/create-user-occupation.dto.ts create mode 100644 src/user-occupation/dtos/update-user-occupation.dto.ts create mode 100644 src/user-occupation/dtos/user-occupation-response.dto.ts create mode 100644 src/user-occupation/user-occupation.controller.ts create mode 100644 src/user-occupation/user-occupation.entity.ts create mode 100644 src/user-occupation/user-occupation.module.ts create mode 100644 src/user-occupation/user-occupation.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index 4253fe6..12c33dd 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -23,6 +23,7 @@ import { databaseConfig } from './shared/configs/database.config'; import { dotenvConfig } from './shared/configs/dotenv.config'; import { GLOBAL_CONFIG } from './shared/constants/global-config.constant'; import { RolesGuard } from './shared/guards/role.guard'; +import { UserOccupationModule } from './user-occupation/user-occupation.module'; import { UserStreak } from './user-streak/user-streak.entity'; import { UserStreakModule } from './user-streak/user-streak.module'; import { User } from './user/user.entity'; @@ -66,6 +67,7 @@ const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); ExamAttemptModule, QuestionModule, QuestionOptionModule, + UserOccupationModule, ], controllers: [AppController], providers: [ diff --git a/src/progress/progress.service.ts b/src/progress/progress.service.ts index f9aab36..3a586f7 100644 --- a/src/progress/progress.service.ts +++ b/src/progress/progress.service.ts @@ -5,7 +5,6 @@ import { FindOneOptions, FindOptionsWhere, Repository } from 'typeorm'; import { CreateProgressDto } from './dtos/create-progress.dto'; import { PaginatedProgressResponseDto } from './dtos/progress-response.dto'; import { UpdateProgressDto } from './dtos/update-progress.dto'; -import { ProgressStatus } from './enums/progress-status.enum'; import { Progress } from './progress.entity'; @Injectable() diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index 7175211..a83f5e8 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -10,6 +10,7 @@ import { Exam } from 'src/exam/exam.entity'; import { Progress } from 'src/progress/progress.entity'; import { QuestionOption } from 'src/question-option/question-option.entity'; import { Question } from 'src/question/question.entity'; +import { UserOccupation } from 'src/user-occupation/user-occupation.entity'; import { UserStreak } from 'src/user-streak/user-streak.entity'; import { User } from 'src/user/user.entity'; import { DataSource, DataSourceOptions } from 'typeorm'; @@ -38,6 +39,7 @@ export const databaseConfig: DataSourceOptions = { ExamAttempt, Question, QuestionOption, + UserOccupation, ], }; diff --git a/src/user-occupation/dtos/create-user-occupation.dto.ts b/src/user-occupation/dtos/create-user-occupation.dto.ts new file mode 100644 index 0000000..b48bb31 --- /dev/null +++ b/src/user-occupation/dtos/create-user-occupation.dto.ts @@ -0,0 +1,22 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class CreateUserOccupationDto { + @IsString() + @IsNotEmpty() + @ApiProperty({ + description: 'Occupation title', + type: String, + example: 'Software Engineer', + }) + title: string; + + @IsString() + @IsNotEmpty() + @ApiProperty({ + description: 'Occupation description', + type: String, + example: 'Software Engineer', + }) + description: string; +} diff --git a/src/user-occupation/dtos/update-user-occupation.dto.ts b/src/user-occupation/dtos/update-user-occupation.dto.ts new file mode 100644 index 0000000..c8a2857 --- /dev/null +++ b/src/user-occupation/dtos/update-user-occupation.dto.ts @@ -0,0 +1,6 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateUserOccupationDto } from './create-user-occupation.dto'; + +export class UpdateUserOccupationDto extends PartialType( + CreateUserOccupationDto, +) {} diff --git a/src/user-occupation/dtos/user-occupation-response.dto.ts b/src/user-occupation/dtos/user-occupation-response.dto.ts new file mode 100644 index 0000000..05c1391 --- /dev/null +++ b/src/user-occupation/dtos/user-occupation-response.dto.ts @@ -0,0 +1,63 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { UserOccupation } from '../user-occupation.entity'; + +export class UserOccupationResponseDto { + @ApiProperty({ + description: 'User occupation ID', + type: String, + }) + id: string; + + @ApiProperty({ + description: 'Occupation title', + type: String, + }) + title: string; + + @ApiProperty({ + description: 'Occupation description', + type: String, + }) + description: string; + + @ApiProperty({ + description: 'Created date', + type: Date, + }) + createdAt: Date; + + @ApiProperty({ + description: 'Updated date', + type: Date, + }) + updatedAt: Date; + + constructor(userOccupation: UserOccupation) { + this.id = userOccupation.id; + this.title = userOccupation.title; + this.description = userOccupation.description; + this.createdAt = userOccupation.createdAt; + this.updatedAt = userOccupation.updatedAt; + } +} + +export class PaginatedUserOccupationResponseDto extends PaginatedResponse( + UserOccupationResponseDto, +) { + constructor( + userOccupations: [UserOccupationResponseDto], + total: number, + page: number, + limit: number, + ) { + super( + userOccupations.map( + (userOccupation) => new UserOccupationResponseDto(userOccupation), + ), + total, + page, + limit, + ); + } +} diff --git a/src/user-occupation/user-occupation.controller.ts b/src/user-occupation/user-occupation.controller.ts new file mode 100644 index 0000000..80381a5 --- /dev/null +++ b/src/user-occupation/user-occupation.controller.ts @@ -0,0 +1,145 @@ +import { + Body, + Controller, + Delete, + Get, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, +} from '@nestjs/common'; +import { + ApiBearerAuth, + ApiParam, + ApiQuery, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { CreateUserOccupationDto } from './dtos/create-user-occupation.dto'; +import { UpdateUserOccupationDto } from './dtos/update-user-occupation.dto'; +import { + PaginatedUserOccupationResponseDto, + UserOccupationResponseDto, +} from './dtos/user-occupation-response.dto'; +import { UserOccupationService } from './user-occupation.service'; + +@Controller('user-occupation') +@ApiTags('User Occupation') +@ApiBearerAuth() +@Injectable() +export class UserOccupationController { + constructor(private readonly userOccupationService: UserOccupationService) {} + + @Post() + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.CREATED, + type: UserOccupationResponseDto, + description: 'Create user occupation', + }) + async create( + @Body() createUserOccupationDto: CreateUserOccupationDto, + ): Promise { + const userOccupation = await this.userOccupationService.create( + createUserOccupationDto, + ); + + return new UserOccupationResponseDto(userOccupation); + } + + @Get() + @ApiResponse({ + status: HttpStatus.OK, + type: PaginatedUserOccupationResponseDto, + description: 'Get all user occupations', + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + async findAll( + @Query() query: PaginateQueryDto, + ): Promise { + return this.userOccupationService.findAll(query); + } + + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + type: UserOccupationResponseDto, + description: 'Get user occupation by ID', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'User occupation ID', + }) + async findOne( + @Param('id', new ParseUUIDPipe()) id: string, + ): Promise { + const userOccupation = await this.userOccupationService.findOne(id, { + where: { id }, + }); + + return new UserOccupationResponseDto(userOccupation); + } + + @Patch(':id') + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + type: UserOccupationResponseDto, + description: 'Update user occupation by ID', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'User occupation ID', + }) + async update( + @Param('id', new ParseUUIDPipe()) id: string, + @Body() updateUserOccupationDto: UpdateUserOccupationDto, + ): Promise { + const userOccupation = await this.userOccupationService.update( + id, + updateUserOccupationDto, + ); + + return new UserOccupationResponseDto(userOccupation); + } + + @Delete(':id') + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + type: UserOccupationResponseDto, + description: 'Delete user occupation by ID', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'User occupation ID', + }) + async remove( + @Param('id', new ParseUUIDPipe()) id: string, + ): Promise { + const userOccupation = await this.userOccupationService.remove(id); + + return new UserOccupationResponseDto(userOccupation); + } +} diff --git a/src/user-occupation/user-occupation.entity.ts b/src/user-occupation/user-occupation.entity.ts new file mode 100644 index 0000000..1139ac5 --- /dev/null +++ b/src/user-occupation/user-occupation.entity.ts @@ -0,0 +1,39 @@ +import { + Column, + CreateDateColumn, + Entity, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +@Entity() +export class UserOccupation { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ + type: String, + nullable: false, + }) + title: string; + + @Column({ + type: String, + nullable: false, + }) + description: string; + + @CreateDateColumn({ + name: 'created_at', + type: 'timestamp', + default: new Date(), + }) + createdAt: Date; + + @UpdateDateColumn({ + name: 'updated_at', + type: 'timestamp', + default: new Date(), + }) + updatedAt: Date; +} diff --git a/src/user-occupation/user-occupation.module.ts b/src/user-occupation/user-occupation.module.ts new file mode 100644 index 0000000..cc994d1 --- /dev/null +++ b/src/user-occupation/user-occupation.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { UserOccupationController } from './user-occupation.controller'; +import { UserOccupation } from './user-occupation.entity'; +import { UserOccupationService } from './user-occupation.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([UserOccupation])], + controllers: [UserOccupationController], + providers: [UserOccupationService], +}) +export class UserOccupationModule {} diff --git a/src/user-occupation/user-occupation.service.ts b/src/user-occupation/user-occupation.service.ts new file mode 100644 index 0000000..d20905a --- /dev/null +++ b/src/user-occupation/user-occupation.service.ts @@ -0,0 +1,83 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { createPagination } from 'src/shared/pagination'; +import { FindOneOptions, FindOptionsWhere, Repository } from 'typeorm'; +import { CreateUserOccupationDto } from './dtos/create-user-occupation.dto'; +import { UpdateUserOccupationDto } from './dtos/update-user-occupation.dto'; +import { PaginatedUserOccupationResponseDto } from './dtos/user-occupation-response.dto'; +import { UserOccupation } from './user-occupation.entity'; + +@Injectable() +export class UserOccupationService { + constructor( + @InjectRepository(UserOccupation) + private readonly userOccupationRepository: Repository, + ) {} + + async create( + createUserOccupationDto: CreateUserOccupationDto, + ): Promise { + const userOccupation = this.userOccupationRepository.create( + createUserOccupationDto, + ); + + return this.userOccupationRepository.save(userOccupation); + } + + async findAll({ + page = 1, + limit = 20, + }: { + page?: number; + limit?: number; + }): Promise { + const { find } = await createPagination(this.userOccupationRepository, { + page, + limit, + }); + + const userOccupations = await find({}).run(); + + return userOccupations; + } + + async findOne( + id: string, + options: FindOneOptions, + ): Promise { + const baseWhere = options.where as FindOptionsWhere; + const whereCondition = { ...baseWhere, id }; + + const userOccupation = await this.userOccupationRepository.findOne({ + where: whereCondition, + }); + + if (!userOccupation) { + throw new NotFoundException('User occupation not found'); + } + + return userOccupation; + } + + async update( + id: string, + updateUserOccupationDto: UpdateUserOccupationDto, + ): Promise { + const userOccupation = await this.findOne(id, {}); + + this.userOccupationRepository.merge( + userOccupation, + updateUserOccupationDto, + ); + + return this.userOccupationRepository.save(userOccupation); + } + + async remove(id: string): Promise { + const userOccupation = await this.findOne(id, { where: { id } }); + + await this.userOccupationRepository.remove(userOccupation); + + return userOccupation; + } +} From d65d957700ed4c4707586d7b7baf95f81083f378 Mon Sep 17 00:00:00 2001 From: khris-xp Date: Sun, 17 Nov 2024 16:56:43 +0700 Subject: [PATCH 065/155] feat: user background topic module service --- src/app.module.ts | 2 + src/progress/progress.controller.ts | 1 - src/shared/configs/database.config.ts | 2 + .../dtos/create-user-background-topic.dto.ts | 27 ++++ .../dtos/update-user-background-topic.dto.ts | 6 + .../dtos/user-background-response.dto.ts | 72 ++++++++++ .../enums/user-background-topic-level.enum.ts | 5 + .../user-background-topic.controller.ts | 136 ++++++++++++++++++ .../user-background-topic.entity.ts | 46 ++++++ .../user-background-topic.module.ts | 12 ++ .../user-background-topic.service.ts | 94 ++++++++++++ 11 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 src/user-background-topic/dtos/create-user-background-topic.dto.ts create mode 100644 src/user-background-topic/dtos/update-user-background-topic.dto.ts create mode 100644 src/user-background-topic/dtos/user-background-response.dto.ts create mode 100644 src/user-background-topic/enums/user-background-topic-level.enum.ts create mode 100644 src/user-background-topic/user-background-topic.controller.ts create mode 100644 src/user-background-topic/user-background-topic.entity.ts create mode 100644 src/user-background-topic/user-background-topic.module.ts create mode 100644 src/user-background-topic/user-background-topic.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index 12c33dd..a1a18cb 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -23,6 +23,7 @@ import { databaseConfig } from './shared/configs/database.config'; import { dotenvConfig } from './shared/configs/dotenv.config'; import { GLOBAL_CONFIG } from './shared/constants/global-config.constant'; import { RolesGuard } from './shared/guards/role.guard'; +import { UserBackgroundTopicModule } from './user-background-topic/user-background-topic.module'; import { UserOccupationModule } from './user-occupation/user-occupation.module'; import { UserStreak } from './user-streak/user-streak.entity'; import { UserStreakModule } from './user-streak/user-streak.module'; @@ -68,6 +69,7 @@ const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); QuestionModule, QuestionOptionModule, UserOccupationModule, + UserBackgroundTopicModule, ], controllers: [AppController], providers: [ diff --git a/src/progress/progress.controller.ts b/src/progress/progress.controller.ts index d3873ed..a3802aa 100644 --- a/src/progress/progress.controller.ts +++ b/src/progress/progress.controller.ts @@ -1,5 +1,4 @@ import { - BadRequestException, Body, Controller, Delete, diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index a83f5e8..6faa123 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -10,6 +10,7 @@ import { Exam } from 'src/exam/exam.entity'; import { Progress } from 'src/progress/progress.entity'; import { QuestionOption } from 'src/question-option/question-option.entity'; import { Question } from 'src/question/question.entity'; +import { UserBackgroundTopic } from 'src/user-background-topic/user-background-topic.entity'; import { UserOccupation } from 'src/user-occupation/user-occupation.entity'; import { UserStreak } from 'src/user-streak/user-streak.entity'; import { User } from 'src/user/user.entity'; @@ -40,6 +41,7 @@ export const databaseConfig: DataSourceOptions = { Question, QuestionOption, UserOccupation, + UserBackgroundTopic, ], }; diff --git a/src/user-background-topic/dtos/create-user-background-topic.dto.ts b/src/user-background-topic/dtos/create-user-background-topic.dto.ts new file mode 100644 index 0000000..1a81a34 --- /dev/null +++ b/src/user-background-topic/dtos/create-user-background-topic.dto.ts @@ -0,0 +1,27 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsEnum, IsNotEmpty } from 'class-validator'; +import { UserBackgroundTopicLevel } from '../enums/user-background-topic-level.enum'; + +export class CreateUserBackgroundTopicDto { + @IsNotEmpty() + @ApiProperty({ + description: 'Title', + type: String, + }) + title: string; + + @IsNotEmpty() + @ApiProperty({ + description: 'Description', + type: String, + }) + description: string; + + @IsEnum(UserBackgroundTopicLevel) + @ApiProperty({ + description: 'Level', + enum: UserBackgroundTopicLevel, + default: UserBackgroundTopicLevel.BEGINNER, + }) + level: UserBackgroundTopicLevel; +} diff --git a/src/user-background-topic/dtos/update-user-background-topic.dto.ts b/src/user-background-topic/dtos/update-user-background-topic.dto.ts new file mode 100644 index 0000000..6410bf6 --- /dev/null +++ b/src/user-background-topic/dtos/update-user-background-topic.dto.ts @@ -0,0 +1,6 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateUserBackgroundTopicDto } from './create-user-background-topic.dto'; + +export class UpdateUserBackgroundTopicDto extends PartialType( + CreateUserBackgroundTopicDto, +) {} diff --git a/src/user-background-topic/dtos/user-background-response.dto.ts b/src/user-background-topic/dtos/user-background-response.dto.ts new file mode 100644 index 0000000..0be2a16 --- /dev/null +++ b/src/user-background-topic/dtos/user-background-response.dto.ts @@ -0,0 +1,72 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { UserBackgroundTopicLevel } from '../enums/user-background-topic-level.enum'; +import { UserBackgroundTopic } from '../user-background-topic.entity'; + +export class UserBackgroundTopicResponseDto { + @ApiProperty({ + description: 'User background topic ID', + type: String, + }) + id: string; + + @ApiProperty({ + description: 'User Background Title', + type: String, + }) + title: string; + + @ApiProperty({ + description: 'User Background Description', + type: String, + }) + description: string; + + @ApiProperty({ + description: 'User Background Level', + enum: UserBackgroundTopicLevel, + }) + level: UserBackgroundTopicLevel; + + @ApiProperty({ + description: 'Created date', + type: Date, + }) + createdAt: Date; + + @ApiProperty({ + description: 'Updated date', + type: Date, + }) + updatedAt: Date; + + constructor(userBackgroundTopic: UserBackgroundTopic) { + this.id = userBackgroundTopic.id; + this.title = userBackgroundTopic.title; + this.description = userBackgroundTopic.description; + this.level = userBackgroundTopic.level; + this.createdAt = userBackgroundTopic.createdAt; + this.updatedAt = userBackgroundTopic.updatedAt; + } +} + +export class PaginatedUserBackgroundTopicResponseDto extends PaginatedResponse( + UserBackgroundTopicResponseDto, +) { + constructor( + userBackgroundTopics: UserBackgroundTopic[], + total: number, + page: number, + limit: number, + ) { + super( + userBackgroundTopics.map( + (userBackgroundTopic) => + new UserBackgroundTopicResponseDto(userBackgroundTopic), + ), + total, + page, + limit, + ); + } +} diff --git a/src/user-background-topic/enums/user-background-topic-level.enum.ts b/src/user-background-topic/enums/user-background-topic-level.enum.ts new file mode 100644 index 0000000..578acc3 --- /dev/null +++ b/src/user-background-topic/enums/user-background-topic-level.enum.ts @@ -0,0 +1,5 @@ +export enum UserBackgroundTopicLevel { + BEGINNER = 'beginner', + INTERMEDIATE = 'intermediate', + ADVANCED = 'advanced', +} diff --git a/src/user-background-topic/user-background-topic.controller.ts b/src/user-background-topic/user-background-topic.controller.ts new file mode 100644 index 0000000..6bdbd73 --- /dev/null +++ b/src/user-background-topic/user-background-topic.controller.ts @@ -0,0 +1,136 @@ +import { + Body, + Controller, + Delete, + Get, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, +} from '@nestjs/common'; +import { + ApiBearerAuth, + ApiParam, + ApiQuery, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { CreateUserBackgroundTopicDto } from './dtos/create-user-background-topic.dto'; +import { UpdateUserBackgroundTopicDto } from './dtos/update-user-background-topic.dto'; +import { + PaginatedUserBackgroundTopicResponseDto, + UserBackgroundTopicResponseDto, +} from './dtos/user-background-response.dto'; +import { UserBackgroundTopicService } from './user-background-topic.service'; + +@Controller('user-background-topic') +@ApiTags('User Background Topic') +@ApiBearerAuth() +@Injectable() +export class UserBackgroundTopicController { + constructor( + private readonly userBackgroundTopicService: UserBackgroundTopicService, + ) {} + + @Get() + @ApiResponse({ + status: HttpStatus.OK, + type: UserBackgroundTopicResponseDto, + description: 'Get all user background topics', + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + async findAll( + @Query() { page, limit }: PaginateQueryDto, + ): Promise { + return this.userBackgroundTopicService.findAll({ page, limit }); + } + + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + type: UserBackgroundTopicResponseDto, + description: 'Get a user background topic by ID', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'User background topic ID', + }) + async findOne( + @Param('id', new ParseUUIDPipe()) id: string, + ): Promise { + return this.userBackgroundTopicService.findOne(id, { where: { id } }); + } + + @Post() + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.CREATED, + type: UserBackgroundTopicResponseDto, + description: 'Create a user background topic', + }) + async create( + @Body() createUserBackgroundTopicDto: CreateUserBackgroundTopicDto, + ): Promise { + return this.userBackgroundTopicService.create(createUserBackgroundTopicDto); + } + + @Patch(':id') + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + type: UserBackgroundTopicResponseDto, + description: 'Update a user background topic by ID', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'User background topic ID', + }) + async update( + @Param('id', new ParseUUIDPipe()) id: string, + @Body() updateUserBackgroundTopicDto: UpdateUserBackgroundTopicDto, + ): Promise { + return this.userBackgroundTopicService.update( + id, + updateUserBackgroundTopicDto, + ); + } + + @Delete(':id') + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Delete a user background topic by ID', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'User background topic ID', + }) + async remove( + @Param('id', new ParseUUIDPipe()) id: string, + ): Promise { + return this.userBackgroundTopicService.remove(id); + } +} diff --git a/src/user-background-topic/user-background-topic.entity.ts b/src/user-background-topic/user-background-topic.entity.ts new file mode 100644 index 0000000..c998afc --- /dev/null +++ b/src/user-background-topic/user-background-topic.entity.ts @@ -0,0 +1,46 @@ +import { + Column, + CreateDateColumn, + Entity, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; +import { UserBackgroundTopicLevel } from './enums/user-background-topic-level.enum'; + +@Entity() +export class UserBackgroundTopic { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ + type: String, + nullable: false, + }) + title: string; + + @Column({ + type: String, + nullable: false, + }) + description: string; + + @Column({ + type: 'enum', + enum: UserBackgroundTopicLevel, + nullable: false, + default: UserBackgroundTopicLevel.BEGINNER, + }) + level: UserBackgroundTopicLevel; + + @CreateDateColumn({ + type: 'timestamp', + name: 'created_at', + }) + createdAt: Date; + + @UpdateDateColumn({ + type: 'timestamp', + name: 'updated_at', + }) + updatedAt: Date; +} diff --git a/src/user-background-topic/user-background-topic.module.ts b/src/user-background-topic/user-background-topic.module.ts new file mode 100644 index 0000000..392ca31 --- /dev/null +++ b/src/user-background-topic/user-background-topic.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { UserBackgroundTopicController } from './user-background-topic.controller'; +import { UserBackgroundTopic } from './user-background-topic.entity'; +import { UserBackgroundTopicService } from './user-background-topic.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([UserBackgroundTopic])], + controllers: [UserBackgroundTopicController], + providers: [UserBackgroundTopicService], +}) +export class UserBackgroundTopicModule {} diff --git a/src/user-background-topic/user-background-topic.service.ts b/src/user-background-topic/user-background-topic.service.ts new file mode 100644 index 0000000..2419598 --- /dev/null +++ b/src/user-background-topic/user-background-topic.service.ts @@ -0,0 +1,94 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { createPagination } from 'src/shared/pagination'; +import { FindOneOptions, FindOptionsWhere, Repository } from 'typeorm'; +import { CreateUserBackgroundTopicDto } from './dtos/create-user-background-topic.dto'; +import { UpdateUserBackgroundTopicDto } from './dtos/update-user-background-topic.dto'; +import { + PaginatedUserBackgroundTopicResponseDto, + UserBackgroundTopicResponseDto, +} from './dtos/user-background-response.dto'; +import { UserBackgroundTopic } from './user-background-topic.entity'; + +@Injectable() +export class UserBackgroundTopicService { + constructor( + @InjectRepository(UserBackgroundTopic) + private readonly userBackgroundTopicRepository: Repository, + ) {} + + async findAll({ + page = 1, + limit = 20, + }: { + page?: number; + limit?: number; + }): Promise { + const { find } = await createPagination( + this.userBackgroundTopicRepository, + { + page, + limit, + }, + ); + + const userBackgroundTopics = await find({}).run(); + + return userBackgroundTopics; + } + + async findOne( + id: string, + options: FindOneOptions, + ): Promise { + const baseWhere = options.where as FindOptionsWhere; + const whereCondition = { ...baseWhere, id }; + + const userBackgroundTopic = + await this.userBackgroundTopicRepository.findOne({ + where: whereCondition, + }); + + if (!userBackgroundTopic) { + throw new NotFoundException('User Background Topic not found'); + } + + return userBackgroundTopic; + } + + async create( + createUserBackgroundTopicDto: CreateUserBackgroundTopicDto, + ): Promise { + const userBackgroundTopic = this.userBackgroundTopicRepository.create( + createUserBackgroundTopicDto, + ); + + await this.userBackgroundTopicRepository.save(userBackgroundTopic); + + return userBackgroundTopic; + } + + async update( + id: string, + updateUserBackgroundTopicDto: UpdateUserBackgroundTopicDto, + ): Promise { + const userBackgroundTopic = await this.findOne(id, { where: { id } }); + + this.userBackgroundTopicRepository.merge( + userBackgroundTopic, + updateUserBackgroundTopicDto, + ); + + await this.userBackgroundTopicRepository.save(userBackgroundTopic); + + return userBackgroundTopic; + } + + async remove(id: string): Promise { + const userBackgroundTopic = await this.findOne(id, { where: { id } }); + + await this.userBackgroundTopicRepository.remove(userBackgroundTopic); + + return userBackgroundTopic; + } +} From 968b6db86f71e5e4bdbfe31399ca60f948a6629e Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sun, 17 Nov 2024 18:27:08 +0700 Subject: [PATCH 066/155] feat: create exam-answer dto and api --- src/app.module.ts | 4 +- .../dtos/create-exam-answer.dto.ts | 14 +- .../dtos/exam-answer-response.dto.ts | 18 +- src/exam-answer/exam-answer.controller.ts | 263 ++++++++++- src/exam-answer/exam-answer.entity.ts | 6 +- src/exam-answer/exam-answer.module.ts | 16 +- src/exam-answer/exam-answer.service.ts | 436 +++++++++++++++--- src/exam/exam.service.ts | 1 - 8 files changed, 664 insertions(+), 94 deletions(-) diff --git a/src/app.module.ts b/src/app.module.ts index cf932ae..dc9549e 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -26,7 +26,7 @@ import { UserModule } from './user/user.module'; import { ExamAttemptModule } from './exam-attempt/exam-attempt.module'; import { QuestionModule } from './question/question.module'; import { QuestionOptionModule } from './question-option/question-option.module'; -import { ExamAnswer } from './exam-answer/exam-answer.entity'; +import { ExamAnswerModule } from './exam-answer/exam-answer.module'; const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); @@ -66,7 +66,7 @@ const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); ExamAttemptModule, QuestionModule, QuestionOptionModule, - ExamAnswer, + ExamAnswerModule, ], controllers: [AppController], providers: [ diff --git a/src/exam-answer/dtos/create-exam-answer.dto.ts b/src/exam-answer/dtos/create-exam-answer.dto.ts index 93ccf33..c42a277 100644 --- a/src/exam-answer/dtos/create-exam-answer.dto.ts +++ b/src/exam-answer/dtos/create-exam-answer.dto.ts @@ -1,22 +1,14 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsBoolean, IsInt, IsNotEmpty } from 'class-validator'; +import { IsBoolean, IsInt, IsNotEmpty, IsOptional } from 'class-validator'; export class CreateExamAnswerDto { - @IsNotEmpty() + @IsOptional() @ApiProperty({ description: 'Exam Attempt ID', type: String, example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', }) - examAttemptId: string; - - @IsNotEmpty() - @ApiProperty({ - description: 'Question ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }) - questionId: string; + examAttemptId?: string; @IsNotEmpty() @ApiProperty({ diff --git a/src/exam-answer/dtos/exam-answer-response.dto.ts b/src/exam-answer/dtos/exam-answer-response.dto.ts index a840569..2d929e7 100644 --- a/src/exam-answer/dtos/exam-answer-response.dto.ts +++ b/src/exam-answer/dtos/exam-answer-response.dto.ts @@ -23,14 +23,25 @@ export class ExamAnswerResponseDto { @ApiProperty({ description: 'Question Data', type: Question, - example: { id: '123e4567-e89b-12d3-a456-426614174000' }, + example: { + id: '1e251a62-6339-4a59-bb56-e338f1dae55b', + question: 'What is this?', + type: 'true_false', + points: 1, + orderIndex: 1, + }, }) question: Question; @ApiProperty({ description: 'Select Question Data', type: QuestionOption, - example: { id: '123e4567-e89b-12d3-a456-426614174000' }, + example: { + id: '9d77c1d9-b0d1-4025-880c-48073e9dc7d5', + questionId: '1e251a62-6339-4a59-bb56-e338f1dae55b', + isCorrect: false, + explanation: 'Rock is not biology.', + }, }) selectedOption: QuestionOption; @@ -71,6 +82,9 @@ export class ExamAnswerResponseDto { constructor(examAnswer: ExamAnswer) { this.id = examAnswer.id; + this.examAttempt = examAnswer.examAttempt; + this.question = examAnswer.question; + this.selectedOption = examAnswer.selectedOption; this.isCorrect = examAnswer.isCorrect; this.points = examAnswer.points; this.createdAt = examAnswer.createdAt; diff --git a/src/exam-answer/exam-answer.controller.ts b/src/exam-answer/exam-answer.controller.ts index 202d1a3..2331133 100644 --- a/src/exam-answer/exam-answer.controller.ts +++ b/src/exam-answer/exam-answer.controller.ts @@ -1,11 +1,264 @@ -import { Controller, Injectable } from '@nestjs/common'; -import { ApiTags, ApiBearerAuth } from '@nestjs/swagger'; +import { + Body, + Controller, + Delete, + Get, + HttpCode, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, +} from '@nestjs/common'; +import { ApiTags, ApiBearerAuth, ApiResponse, ApiQuery } from '@nestjs/swagger'; import { ExamAnswerService } from './exam-answer.service'; +import { + ExamAnswerResponseDto, + PaginatedExamAnswerResponseDto, +} from './dtos/exam-answer-response.dto'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums'; +import { CreateExamAnswerDto } from './dtos/create-exam-answer.dto'; +import { UpdateExamAnswerDto } from './dtos/update-exam-answer.dto'; -@Controller('exam') -@ApiTags('Exam') +@Controller('examAnswer') +@ApiTags('ExamAnswer') @ApiBearerAuth() @Injectable() -export class ExamController { +export class ExamAnswerController { constructor(private readonly examAnswerService: ExamAnswerService) {} + + @Get() + @Roles(Role.TEACHER) + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all exam answers', + type: PaginatedExamAnswerResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findAll( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return await this.examAnswerService.findAll(request, { + page: query.page, + limit: query.limit, + search: query.search, + }); + } + + @Get(':id') + @Roles(Role.TEACHER) + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns an exam answer', + type: ExamAnswerResponseDto, + }) + async findOne( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const examAnswer = await this.examAnswerService.findOne(request, { + where: { id }, + }); + return new ExamAnswerResponseDto(examAnswer); + } + + @Get('question/:questionId') + @Roles(Role.TEACHER) + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all exam answer in question', + type: PaginatedExamAnswerResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findQuestionByExamId( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + @Param( + 'questionId', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + questionId: string, + ): Promise { + return await this.examAnswerService.findExamAnswerByQuestionId( + request, + questionId, + { + page: query.page, + limit: query.limit, + search: query.search, + }, + ); + } + + @Get('selectedOption/:selectedOptionId') + @Roles(Role.TEACHER) + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all exam answer in selectedOption', + type: PaginatedExamAnswerResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findQuestionByselectedOptionId( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + @Param( + 'selectedOptionId', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + selectedOptionId: string, + ): Promise { + return await this.examAnswerService.findExamAnswerBySelectedOptionId( + request, + selectedOptionId, + { + page: query.page, + limit: query.limit, + search: query.search, + }, + ); + } + + @Post() + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Create an exam answer', + type: ExamAnswerResponseDto, + }) + @HttpCode(HttpStatus.CREATED) + async createExamAnswer( + @Body() createExamAnswerDto: CreateExamAnswerDto, + ): Promise { + const examAnswer = + await this.examAnswerService.createExamAnswer(createExamAnswerDto); + return new ExamAnswerResponseDto(examAnswer); + } + + @Patch(':id') + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Update an exam answer', + type: ExamAnswerResponseDto, + }) + async updateExamAnswer( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @Body() updateExamAnswerDto: UpdateExamAnswerDto, + ): Promise { + const examAnswer = await this.examAnswerService.updateExamAnswer( + request, + id, + updateExamAnswerDto, + ); + return new ExamAnswerResponseDto(examAnswer); + } + + @Delete(':id') + @Roles(Role.TEACHER) + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Delete an exam', + }) + @HttpCode(HttpStatus.NO_CONTENT) + async deleteExam( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise<{ massage: string }> { + await this.examAnswerService.deleteExamAnswer(request, id); + return { massage: 'Exam answer deleted successfully' }; + } } diff --git a/src/exam-answer/exam-answer.entity.ts b/src/exam-answer/exam-answer.entity.ts index 8440f25..eed19ef 100644 --- a/src/exam-answer/exam-answer.entity.ts +++ b/src/exam-answer/exam-answer.entity.ts @@ -8,22 +8,24 @@ import { JoinColumn, ManyToOne, PrimaryGeneratedColumn, + Unique, UpdateDateColumn, } from 'typeorm'; @Entity() +@Unique(['selectedOptionId', 'questionId']) export class ExamAnswer { @PrimaryGeneratedColumn('uuid') id: string; @ManyToOne(() => ExamAttempt, (examAttempt) => examAttempt.examAnswer, { onDelete: 'CASCADE', - nullable: false, + nullable: true, }) @JoinColumn({ name: 'exam_attempt_id' }) examAttempt: ExamAttempt; - @Column({ name: 'exam_attempt_id' }) + @Column({ name: 'exam_attempt_id', nullable: true }) examAttemptId: string; @ManyToOne(() => Question, (question) => question.examAnswer, { diff --git a/src/exam-answer/exam-answer.module.ts b/src/exam-answer/exam-answer.module.ts index 0b4290c..6197e1c 100644 --- a/src/exam-answer/exam-answer.module.ts +++ b/src/exam-answer/exam-answer.module.ts @@ -5,10 +5,22 @@ import { ExamAnswer } from './exam-answer.entity'; import { ExamAttemptController } from 'src/exam-attempt/exam-attempt.controller'; import { examAnswerProviders } from './exam-answer.providers'; import { ExamAnswerService } from './exam-answer.service'; +import { ExamAnswerController } from './exam-answer.controller'; +import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; +import { Question } from 'src/question/question.entity'; +import { QuestionOption } from 'src/question-option/question-option.entity'; @Module({ - imports: [DatabaseModule, TypeOrmModule.forFeature([ExamAnswer])], - controllers: [ExamAttemptController], + imports: [ + DatabaseModule, + TypeOrmModule.forFeature([ + ExamAnswer, + ExamAttempt, + Question, + QuestionOption, + ]), + ], + controllers: [ExamAnswerController], providers: [...examAnswerProviders, ExamAnswerService], exports: [ExamAnswerService], }) diff --git a/src/exam-answer/exam-answer.service.ts b/src/exam-answer/exam-answer.service.ts index b47a8ab..a1fec4e 100644 --- a/src/exam-answer/exam-answer.service.ts +++ b/src/exam-answer/exam-answer.service.ts @@ -1,83 +1,381 @@ -import { Injectable, Inject } from '@nestjs/common'; -import { FindOptionsWhere, ILike, Repository } from 'typeorm'; +import { + Injectable, + Inject, + NotFoundException, + BadRequestException, + ConflictException, + ForbiddenException, +} from '@nestjs/common'; +import { + FindOneOptions, + FindOptionsSelect, + FindOptionsWhere, + ILike, + Repository, +} from 'typeorm'; import { ExamAnswer } from './exam-answer.entity'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { PaginatedExamAnswerResponseDto } from './dtos/exam-answer-response.dto'; import { createPagination } from 'src/shared/pagination'; import { ExamStatus, Role } from 'src/shared/enums'; +import { CreateExamAnswerDto } from './dtos/create-exam-answer.dto'; +import { Question } from 'src/question/question.entity'; +import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; +import { QuestionOption } from 'src/question-option/question-option.entity'; +import { UpdateExamAnswerDto } from './dtos/update-exam-answer.dto'; @Injectable() export class ExamAnswerService { constructor( @Inject('ExamAnswerRepository') private readonly examAnswerRepository: Repository, + @Inject('ExamAttemptRepository') + private readonly examAttemptRepository: Repository, + @Inject('QuestionRepository') + private readonly questionRepository: Repository, + @Inject('QuestionOptionRepository') + private readonly questionOptionRepository: Repository, ) {} - // async findAll( - // request: AuthenticatedRequest, - // { - // page = 1, - // limit = 20, - // search = '', - // }: { - // page?: number; - // limit?: number; - // search?: string; - // }, - // ): Promise { - // const { find } = await createPagination(this.examAnswerRepository, { - // page, - // limit, - // }); - - // const whereCondition = this.validateAndCreateCondition(request, search); - // const exam = await find({ - // where: whereCondition, - // }).run(); - - // return exam; - // } - - // private validateAndCreateCondition( - // request: AuthenticatedRequest, - // search: string, - // ): FindOptionsWhere | FindOptionsWhere[] { - // const baseSearch = search ? { answerText: ILike(`%${search}%`) } : {}; - - // if (request.user.role === Role.STUDENT) { - // return { - // ...baseSearch, - // question: { exam: { status: ExamStatus.PUBLISHED } }, - // }; - // } - - // if (request.user.role === Role.TEACHER) { - // return [ - // { - // ...baseSearch, - // question: { - // exam: { - // courseModule: { - // course: { - // teacher: { - // id: request.user.id, - // }, - // }, - // }, - // }, - // }, - // }, - // { - // ...baseSearch, - // question: { exam: { status: ExamStatus.PUBLISHED } }, - // }, - // ]; - // } - - // if (request.user.role === Role.ADMIN) { - // return { ...baseSearch }; - // } - - // return { ...baseSearch, status: ExamStatus.PUBLISHED }; - // } + async findAll( + request: AuthenticatedRequest, + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.examAnswerRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateCondition(request, search); + const exam = await find({ + where: whereCondition, + relations: ['examAttempt', 'question', 'selectedOption'], + select: { + examAttempt: this.selectPopulateExamAttempt(), + question: this.selectPopulateQuestion(), + selectedOption: this.selectPopulateSelectedOption(), + }, + }).run(); + + return exam; + } + + private validateAndCreateCondition( + request: AuthenticatedRequest, + search: string, + ): FindOptionsWhere | FindOptionsWhere[] { + const baseSearch = search ? { answerText: ILike(`%${search}%`) } : {}; + + if (request.user.role === Role.TEACHER) { + return [ + { + ...baseSearch, + question: { + exam: { + courseModule: { + course: { + teacher: { + id: request.user.id, + }, + }, + }, + }, + }, + }, + ]; + } + + if (request.user.role === Role.ADMIN) { + return { ...baseSearch }; + } + + return [ + { + ...baseSearch, + question: { + exam: { + courseModule: { + course: { + teacher: { + id: request.user.id, + }, + }, + }, + }, + }, + }, + ]; + } + + async findOne( + request: AuthenticatedRequest, + options: FindOneOptions = {}, + ): Promise { + const whereCondition = this.validateAndCreateCondition(request, ''); + + const where = Array.isArray(whereCondition) + ? [ + { ...whereCondition[0], ...options.where }, + { ...whereCondition[1], ...options.where }, + ] + : { ...whereCondition, ...options.where }; + + const examAnswer = await this.examAnswerRepository.findOne({ + ...options, + where, + relations: ['examAttempt', 'question', 'selectedOption'], + select: { + examAttempt: this.selectPopulateExamAttempt(), + question: this.selectPopulateQuestion(), + selectedOption: this.selectPopulateSelectedOption(), + }, + }); + + if (!examAnswer) { + throw new NotFoundException('Exam answer not found'); + } + + return examAnswer; + } + + async findExamAnswerByQuestionId( + request: AuthenticatedRequest, + questionId: string, + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.examAnswerRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateCondition(request, search); + whereCondition['question'] = { id: questionId }; + + const question = await this.questionRepository.findOne({ + where: { id: questionId }, + }); + + if (!question) { + throw new NotFoundException('Question not found.'); + } + + return await find({ + where: whereCondition, + relations: ['examAttempt', 'question', 'selectedOption'], + select: { + examAttempt: this.selectPopulateExamAttempt(), + question: this.selectPopulateQuestion(), + selectedOption: this.selectPopulateSelectedOption(), + }, + }).run(); + } + + async findExamAnswerBySelectedOptionId( + request: AuthenticatedRequest, + selectedOptionId: string, + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.examAnswerRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateCondition(request, search); + whereCondition['selectedOption'] = { id: selectedOptionId }; + + const selectedOption = await this.questionOptionRepository.findOne({ + where: { id: selectedOptionId }, + }); + + if (!selectedOption) { + throw new NotFoundException('Selected option not found.'); + } + + return await find({ + where: whereCondition, + relations: ['examAttempt', 'question', 'selectedOption'], + select: { + examAttempt: this.selectPopulateExamAttempt(), + question: this.selectPopulateQuestion(), + selectedOption: this.selectPopulateSelectedOption(), + }, + }).run(); + } + + async createExamAnswer( + createExamAnswerDto: CreateExamAnswerDto, + ): Promise { + const selectedOption = await this.questionOptionRepository.findOne({ + where: { id: createExamAnswerDto.selectedOptionId }, + select: this.selectPopulateSelectedOption(), + }); + + if (!selectedOption) + throw new NotFoundException('Not Found SelectedOption'); + + const question = await this.questionRepository.findOne({ + where: { id: selectedOption.questionId }, + select: this.selectPopulateQuestion(), + }); + + if (!question) throw new NotFoundException('Not Found Question'); + + const examAttempt = createExamAnswerDto.examAttemptId + ? await this.examAttemptRepository.findOne({ + where: { id: createExamAnswerDto.examAttemptId }, + select: this.selectPopulateExamAttempt(), + }) + : null; + + const examAnswer = await this.examAnswerRepository.create({ + ...createExamAnswerDto, + selectedOption, + question, + examAttempt, + }); + + if (!examAnswer) throw new NotFoundException("Can't create exam"); + try { + await this.examAnswerRepository.save(examAnswer); + return examAnswer; + } catch (error) { + if (error.code === '23505') { + throw new ConflictException( + 'An exam answer for this question and selected option already exists.', + ); + } + throw error; + } + } + + async updateExamAnswer( + request: AuthenticatedRequest, + id: string, + updateExamAnswerDto: UpdateExamAnswerDto, + ): Promise { + const examAnswerInData = await this.findOne(request, { where: { id } }); + if (!examAnswerInData) throw new NotFoundException('Exam answer not found'); + + let examAttempt = null; + if (updateExamAnswerDto.examAttemptId) { + examAttempt = await this.examAttemptRepository.findOne({ + where: { id: updateExamAnswerDto.examAttemptId }, + select: this.selectPopulateExamAttempt(), + }); + if (!examAttempt) { + throw new NotFoundException('Exam attempt not found'); + } + } + + let selectedOption = null; + let question = null; + + if (updateExamAnswerDto.selectedOptionId) { + selectedOption = await this.questionOptionRepository.findOne({ + where: { id: updateExamAnswerDto.selectedOptionId }, + select: this.selectPopulateSelectedOption(), + }); + if (!selectedOption) { + throw new NotFoundException('SelectedOption not found'); + } + + question = await this.questionRepository.findOne({ + where: { id: selectedOption.questionId }, + select: this.selectPopulateQuestion(), + }); + + if (!question) { + throw new NotFoundException('Question not found'); + } + } + + const updateExamAnswer = { + ...updateExamAnswerDto, + ...(examAttempt ? { examAttemptId: examAttempt.id } : {}), + ...(selectedOption ? { selectedOptionId: selectedOption.id } : {}), + ...(question ? { questionId: selectedOption.questionId } : {}), + }; + + const examAnswer = await this.examAnswerRepository.update( + id, + updateExamAnswer, + ); + if (!examAnswer) throw new NotFoundException("Can't update exam answer"); + return await this.examAnswerRepository.findOne({ + where: { id }, + relations: ['examAttempt', 'question', 'selectedOption'], + select: { + examAttempt: this.selectPopulateExamAttempt(), + question: this.selectPopulateQuestion(), + selectedOption: this.selectPopulateSelectedOption(), + }, + }); + } + + async deleteExamAnswer( + request: AuthenticatedRequest, + id: string, + ): Promise { + try { + if (await this.findOne(request, { where: { id } })) { + await this.examAnswerRepository.delete(id); + } + } catch (error) { + if (error instanceof Error) + throw new NotFoundException('Exam answer not found'); + } + } + + private selectPopulateExamAttempt(): FindOptionsSelect { + return { + id: true, + score: true, + status: true, + startedAt: true, + submittedAt: true, + }; + } + + private selectPopulateQuestion(): FindOptionsSelect { + return { + id: true, + question: true, + type: true, + points: true, + orderIndex: true, + }; + } + + private selectPopulateSelectedOption(): FindOptionsSelect { + return { + id: true, + isCorrect: true, + explanation: true, + questionId: true, + }; + } } diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts index 717c0fb..2e80c0d 100644 --- a/src/exam/exam.service.ts +++ b/src/exam/exam.service.ts @@ -121,7 +121,6 @@ export class ExamService { async createExam(createExamDto: CreateExamDto): Promise { const courseModule = await this.courseModuleRepository.findOne({ where: { id: createExamDto.courseModuleId }, - relations: ['courseModule'], select: this.selectPopulateCourseModule(), }); From 0e4376a98a650cbd322341c201d8d645ea13a6e7 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sun, 17 Nov 2024 18:34:10 +0700 Subject: [PATCH 067/155] fix: bug when populate --- src/exam/exam.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts index 717c0fb..2e80c0d 100644 --- a/src/exam/exam.service.ts +++ b/src/exam/exam.service.ts @@ -121,7 +121,6 @@ export class ExamService { async createExam(createExamDto: CreateExamDto): Promise { const courseModule = await this.courseModuleRepository.findOne({ where: { id: createExamDto.courseModuleId }, - relations: ['courseModule'], select: this.selectPopulateCourseModule(), }); From e9be15202c790ed97d4333e893e5d8c0977f43ec Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sun, 17 Nov 2024 19:00:05 +0700 Subject: [PATCH 068/155] feat: add populate in update --- src/exam-attempt/exam-attempt.service.dto.ts | 2 +- src/exam/exam.service.ts | 32 ++++++++++++++++--- .../question-option.service.ts | 17 ++++++++-- src/question/question.service.ts | 21 +++++++++--- 4 files changed, 60 insertions(+), 12 deletions(-) diff --git a/src/exam-attempt/exam-attempt.service.dto.ts b/src/exam-attempt/exam-attempt.service.dto.ts index 6e0db6f..149e92f 100644 --- a/src/exam-attempt/exam-attempt.service.dto.ts +++ b/src/exam-attempt/exam-attempt.service.dto.ts @@ -184,7 +184,7 @@ export class ExamAttemptService { throw new ForbiddenException( "Can't create exam-attempt more than max attempt", ); - const examAttempt = this.examAttemptRepository.create({ + const examAttempt = await this.examAttemptRepository.create({ ...createExamAttemptDto, exam, user, diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts index 2e80c0d..77be4e0 100644 --- a/src/exam/exam.service.ts +++ b/src/exam/exam.service.ts @@ -1,4 +1,9 @@ -import { Injectable, Inject, NotFoundException } from '@nestjs/common'; +import { + Injectable, + Inject, + NotFoundException, + BadRequestException, +} from '@nestjs/common'; import { Repository, FindOneOptions, @@ -124,10 +129,15 @@ export class ExamService { select: this.selectPopulateCourseModule(), }); - const exam = this.examRepository.create({ ...createExamDto, courseModule }); + if (!courseModule) throw new NotFoundException('Course Module not found'); + + const exam = await this.examRepository.create({ + ...createExamDto, + courseModule, + }); - await this.examRepository.save(exam); if (!exam) throw new NotFoundException("Can't create exam"); + await this.examRepository.save(exam); return exam; } @@ -143,7 +153,21 @@ export class ExamService { ) { throw new NotFoundException("Can't change status to draft"); } - const exam = await this.examRepository.update(id, updateExamDto); + let courseModule = null; + if (updateExamDto.courseModuleId) { + courseModule = await this.courseModuleRepository.findOne({ + where: { id: updateExamDto.courseModuleId }, + select: this.selectPopulateCourseModule(), + }); + + if (!courseModule) throw new NotFoundException('CourseModule Not Found'); + } + const updateExam = { + ...updateExamDto, + ...(courseModule ? { examAttemptId: courseModule.id } : {}), + }; + + const exam = await this.examRepository.update(id, updateExam); if (!exam) throw new NotFoundException("Can't update exam"); return await this.examRepository.findOne({ where: { id }, diff --git a/src/question-option/question-option.service.ts b/src/question-option/question-option.service.ts index 5fb2993..a13f9be 100644 --- a/src/question-option/question-option.service.ts +++ b/src/question-option/question-option.service.ts @@ -186,7 +186,7 @@ export class QuestionOptionService { if (!question) { throw new NotFoundException('Question option not found.'); } - const questionOption = this.questionOptionRepository.create({ + const questionOption = await this.questionOptionRepository.create({ ...createQuestionOptionDto, question, }); @@ -203,9 +203,22 @@ export class QuestionOptionService { updateQuestionOptionDto: UpdateQuestionOptionDto, ): Promise { await this.findOne(request, { where: { id } }); + let question = null; + if (updateQuestionOptionDto.questionId) { + question = await this.questionRepository.findOne({ + where: { id: updateQuestionOptionDto.questionId }, + select: this.selectPopulateQuestion(), + }); + + if (!question) throw new NotFoundException('Question Not Found'); + } + const updateQuestionOption = { + ...updateQuestionOptionDto, + ...(question ? { questionId: question.id } : {}), + }; const questionOption = await this.questionOptionRepository.update( id, - updateQuestionOptionDto, + updateQuestionOption, ); if (!questionOption) throw new NotFoundException("Can't update question option"); diff --git a/src/question/question.service.ts b/src/question/question.service.ts index e567def..bf7d9e3 100644 --- a/src/question/question.service.ts +++ b/src/question/question.service.ts @@ -232,10 +232,11 @@ export class QuestionService { if (!exam) { throw new NotFoundException('Exam not found.'); } - const question = this.questionRepository.create({ + const question = await this.questionRepository.create({ ...createQuestionDto, exam, }); + if (!question) throw new BadRequestException('Can not create question'); try { await this.questionRepository.save(question); return question; @@ -257,11 +258,21 @@ export class QuestionService { updateQuestionDto: UpdateQuestionDto, ): Promise { await this.findOne(request, { where: { id } }); + let exam = null; + if (updateQuestionDto.examId) { + exam = await this.examRepository.findOne({ + where: { id: updateQuestionDto.examId }, + select: this.selectPopulateExam(), + }); + + if (!exam) throw new NotFoundException('Exam Not Found'); + } + const updateQuestion = { + ...updateQuestionDto, + ...(exam ? { examId: exam.id } : {}), + }; try { - const question = await this.questionRepository.update( - id, - updateQuestionDto, - ); + const question = await this.questionRepository.update(id, updateQuestion); if (!question) throw new NotFoundException("Can't update question"); return await this.questionRepository.findOne({ where: { id }, From 3fb1ef1650dd8311b1e57972f22b4405f323d5c2 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sun, 17 Nov 2024 20:30:32 +0700 Subject: [PATCH 069/155] feat: sent only role and id in function --- src/exam-attempt/exam-attempt.controller.ts | 41 +++++++++---- src/exam-attempt/exam-attempt.service.dto.ts | 63 ++++++++++++-------- 2 files changed, 67 insertions(+), 37 deletions(-) diff --git a/src/exam-attempt/exam-attempt.controller.ts b/src/exam-attempt/exam-attempt.controller.ts index 1315192..c978cea 100644 --- a/src/exam-attempt/exam-attempt.controller.ts +++ b/src/exam-attempt/exam-attempt.controller.ts @@ -62,11 +62,15 @@ export class ExamAttemptController { @Req() request: AuthenticatedRequest, @Query() query: PaginateQueryDto, ): Promise { - return await this.examAttemptService.findAll(request, { - page: query.page, - limit: query.limit, - search: query.search, - }); + return await this.examAttemptService.findAll( + request.user.id, + request.user.role, + { + page: query.page, + limit: query.limit, + search: query.search, + }, + ); } @Get(':id') @@ -86,9 +90,13 @@ export class ExamAttemptController { ) id: string, ): Promise { - const exam = await this.examAttemptService.findOne(request, { - where: { id }, - }); + const exam = await this.examAttemptService.findOne( + request.user.id, + request.user.role, + { + where: { id }, + }, + ); return new ExamAttemptResponseDto(exam); } @@ -105,7 +113,7 @@ export class ExamAttemptController { @Body() createExamAttemptDto: CreateExamAttemptDto, ): Promise { const exam = await this.examAttemptService.createExamAttempt( - request, + request.user.id, createExamAttemptDto, ); return new ExamAttemptResponseDto(exam); @@ -131,7 +139,8 @@ export class ExamAttemptController { @Body() updateExamAttemptDto: UpdateExamAttemptDto, ): Promise { const exam = await this.examAttemptService.updateExamAttempt( - request, + request.user.id, + request.user.role, id, updateExamAttemptDto, ); @@ -156,7 +165,11 @@ export class ExamAttemptController { ) id: string, ): Promise { - const exam = await this.examAttemptService.submittedExam(request, id); + const exam = await this.examAttemptService.submittedExam( + request.user.id, + request.user.role, + id, + ); return new ExamAttemptResponseDto(exam); } @@ -179,7 +192,11 @@ export class ExamAttemptController { ) id: string, ): Promise<{ massage: string }> { - await this.examAttemptService.deleteExamAttempt(request, id); + await this.examAttemptService.deleteExamAttempt( + request.user.id, + request.user.role, + id, + ); return { massage: 'Exam-attempt deleted successfully' }; } } diff --git a/src/exam-attempt/exam-attempt.service.dto.ts b/src/exam-attempt/exam-attempt.service.dto.ts index e44c8b3..85decc7 100644 --- a/src/exam-attempt/exam-attempt.service.dto.ts +++ b/src/exam-attempt/exam-attempt.service.dto.ts @@ -14,7 +14,6 @@ import { Repository, } from 'typeorm'; import { ExamAttempt } from './exam-attempt.entity'; -import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { PaginatedExamAttemptResponseDto } from './dtos/exam-attempt-response.dto'; import { createPagination } from 'src/shared/pagination'; import { ExamAttemptStatus, ExamStatus, Role } from 'src/shared/enums'; @@ -35,7 +34,8 @@ export class ExamAttemptService { ) {} async findAll( - request: AuthenticatedRequest, + userId: string, + role: Role, { page = 1, limit = 20, @@ -51,7 +51,11 @@ export class ExamAttemptService { limit, }); - const whereCondition = this.validateAndCreateCondition(request, search); + const whereCondition = this.validateAndCreateCondition( + userId, + role, + search, + ); const exam = await find({ where: whereCondition, relations: ['exam', 'user'], @@ -65,33 +69,34 @@ export class ExamAttemptService { } private validateAndCreateCondition( - request: AuthenticatedRequest, + userId: string, + role: Role, search: string, ): FindOptionsWhere | FindOptionsWhere[] { const baseSearch = search ? { id: ILike(`%${search}%`) } : {}; - if (request.user.role === Role.ADMIN) { + if (role === Role.ADMIN) { return { ...baseSearch }; } - if (request.user.role === Role.STUDENT) { + if (role === Role.STUDENT) { return { ...baseSearch, - userId: request.user.id, + userId: userId, exam: { status: ExamStatus.PUBLISHED, }, }; } - if (request.user.role === Role.TEACHER) { + if (role === Role.TEACHER) { return { ...baseSearch, exam: { courseModule: { course: { teacher: { - id: request.user.id, + id: userId, }, }, }, @@ -101,7 +106,7 @@ export class ExamAttemptService { return { ...baseSearch, - userId: request.user.id, + userId: userId, exam: { status: ExamStatus.PUBLISHED, }, @@ -109,10 +114,11 @@ export class ExamAttemptService { } async findOne( - request: AuthenticatedRequest, + userId: string, + role: Role, options: FindOneOptions = {}, ): Promise { - const whereCondition = this.validateAndCreateCondition(request, ''); + const whereCondition = this.validateAndCreateCondition(userId, role, ''); const where = Array.isArray(whereCondition) ? [ @@ -139,7 +145,7 @@ export class ExamAttemptService { } async createExamAttempt( - request: AuthenticatedRequest, + userId: string, createExamAttemptDto: CreateExamAttemptDto, ): Promise { const exam = await this.examRepository.findOne({ @@ -150,7 +156,7 @@ export class ExamAttemptService { throw new NotFoundException('Exam not found.'); } const user = await this.userRepository.findOne({ - where: { id: request.user.id }, + where: { id: userId }, select: this.selectPopulateUser(), }); if (!user) { @@ -159,10 +165,8 @@ export class ExamAttemptService { if (user.role != Role.STUDENT) throw new ForbiddenException('User is not student.'); if ( - (await this.countExamAttempts( - createExamAttemptDto.examId, - request.user.id, - )) >= exam.maxAttempts + (await this.countExamAttempts(createExamAttemptDto.examId, userId)) >= + exam.maxAttempts ) throw new ForbiddenException( "Can't create exam-attempt more than max attempt", @@ -178,11 +182,14 @@ export class ExamAttemptService { } async updateExamAttempt( - request: AuthenticatedRequest, + userId: string, + role: Role, id: string, updateExamAttemptDto: UpdateExamAttemptDto, ): Promise { - const examAttemptInData = await this.findOne(request, { where: { id } }); + const examAttemptInData = await this.findOne(userId, role, { + where: { id }, + }); if ( examAttemptInData.status != ExamAttemptStatus.IN_PROGRESS && updateExamAttemptDto.status == ExamAttemptStatus.IN_PROGRESS @@ -205,11 +212,12 @@ export class ExamAttemptService { } async deleteExamAttempt( - request: AuthenticatedRequest, + userId: string, + role: Role, id: string, ): Promise { try { - if (await this.findOne(request, { where: { id } })) { + if (await this.findOne(userId, role, { where: { id } })) { await this.examAttemptRepository.delete(id); } } catch (error) { @@ -219,16 +227,21 @@ export class ExamAttemptService { } async submittedExam( - request: AuthenticatedRequest, + userId: string, + role: Role, id: string, ): Promise { - const examAttemptInData = await this.findOne(request, { where: { id } }); + const examAttemptInData = await this.findOne(userId, role, { + where: { id }, + }); examAttemptInData.submittedAt = new Date(); await this.examAttemptRepository.update(id, examAttemptInData); - const updatedExamAttempt = await this.findOne(request, { where: { id } }); + const updatedExamAttempt = await this.findOne(userId, role, { + where: { id }, + }); return updatedExamAttempt; } From e681927671ecb0f75dfc8eb3dd9d1b9fb77a5e78 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Sun, 17 Nov 2024 21:08:05 +0700 Subject: [PATCH 070/155] feat: enhance chat room and enrollment modules; add ownership guard and update service exports --- .../authenticated-request.interface.ts | 3 +- src/chapter/chapter.module.ts | 2 + src/chapter/chapter.service.ts | 273 +++++++++--------- src/chapter/dtos/chapter-response.dto.ts | 178 ++++++------ src/chat-message/chat-message.entity.ts | 1 + src/chat-room/chat-room.controller.ts | 6 +- src/chat-room/chat-room.module.ts | 3 + .../guards/chat-room-ownership.guard.ts | 39 +++ src/enrollment/enrollment.controller.ts | 201 +++++++------ src/enrollment/enrollment.module.ts | 1 + src/enrollment/enrollment.service.ts | 132 ++++----- 11 files changed, 451 insertions(+), 388 deletions(-) create mode 100644 src/chat-room/guards/chat-room-ownership.guard.ts diff --git a/src/auth/interfaces/authenticated-request.interface.ts b/src/auth/interfaces/authenticated-request.interface.ts index a0ec097..8cc70df 100644 --- a/src/auth/interfaces/authenticated-request.interface.ts +++ b/src/auth/interfaces/authenticated-request.interface.ts @@ -1,5 +1,6 @@ import { JwtPayloadDto } from '../dtos/jwt-payload.dto'; +import { Request } from 'express'; export interface AuthenticatedRequest extends Request { - user: JwtPayloadDto; + user: JwtPayloadDto; } diff --git a/src/chapter/chapter.module.ts b/src/chapter/chapter.module.ts index b858f41..bdd122c 100644 --- a/src/chapter/chapter.module.ts +++ b/src/chapter/chapter.module.ts @@ -6,11 +6,13 @@ import { chapterProviders } from './chapter.provider'; import { ChapterService } from './chapter.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { CourseModule } from 'src/course-module/course-module.entity'; +import { ChatRoomModule } from 'src/chat-room/chat-room.module'; @Module({ imports: [ DatabaseModule, TypeOrmModule.forFeature([Chapter, CourseModule]), + ChatRoomModule, ], controllers: [ChapterController], providers: [...chapterProviders, ChapterService], diff --git a/src/chapter/chapter.service.ts b/src/chapter/chapter.service.ts index 9e06bd4..879967a 100644 --- a/src/chapter/chapter.service.ts +++ b/src/chapter/chapter.service.ts @@ -1,7 +1,7 @@ import { - BadRequestException, - Injectable, - NotFoundException, + BadRequestException, + Injectable, + NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { createPagination } from 'src/shared/pagination'; @@ -10,153 +10,168 @@ import { Chapter } from './chapter.entity'; import { PaginatedChapterResponseDto } from './dtos/chapter-response.dto'; import { CreateChapterDto } from './dtos/create-chapter.dto'; import { UpdateChapterDto } from './dtos/update-chapter.dto'; +import { ChatRoomService } from 'src/chat-room/chat-room.service'; +import { ChatRoomStatus, ChatRoomType } from 'src/chat-room/enums'; @Injectable() export class ChapterService { - constructor( - @InjectRepository(Chapter) - private readonly chapterRepository: Repository, - ) {} - - async findAll({ - page = 1, - limit = 20, - search = '', - }: { - page?: number; - limit?: number; - search?: string; - }): Promise { - const { find } = await createPagination(this.chapterRepository, { - page, - limit, - }); - - const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; - const whereCondition = { ...baseSearch }; - - const chapters = await find({ - where: whereCondition, - relations: { - module: true, - }, - }).run(); - - return chapters; - } - - async findOne( - id: string, - options: FindOneOptions, - ): Promise { - const baseWhere = options.where as FindOptionsWhere; - const whereCondition = { ...baseWhere, id }; - - const chapter = await this.chapterRepository.findOne({ - where: whereCondition, - relations: { - module: true, - }, - }); - - if (!chapter) { - throw new NotFoundException('Chapter not found'); + constructor( + @InjectRepository(Chapter) + private readonly chapterRepository: Repository, + private readonly chatRoomService: ChatRoomService, + ) { } + + async findAll({ + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }): Promise { + const { find } = await createPagination(this.chapterRepository, { + page, + limit, + }); + + const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; + const whereCondition = { ...baseSearch }; + + const chapters = await find({ + where: whereCondition, + relations: { + module: true, + }, + }).run(); + + return chapters; } - return chapter; - } - - async validateAndGetNextOrderIndex(moduleId: string): Promise { - const existingChapter = await this.chapterRepository.find({ - where: { moduleId }, - order: { orderIndex: 'DESC' }, - }); - - const nextOrderIndex = existingChapter.map((chapter) => chapter.orderIndex); - const hasDuplicates = - new Set(nextOrderIndex).size !== nextOrderIndex.length; - - if (hasDuplicates) { - throw new BadRequestException('Order index is duplicated'); + async findOne( + id: string, + options: FindOneOptions, + ): Promise { + const baseWhere = options.where as FindOptionsWhere; + const whereCondition = { ...baseWhere, id }; + + const chapter = await this.chapterRepository.findOne({ + where: whereCondition, + relations: { + module: true, + }, + }); + + if (!chapter) { + throw new NotFoundException('Chapter not found'); + } + + return chapter; } - return nextOrderIndex.length ? nextOrderIndex[0] + 1 : 1; - } - - async create(createChapterDto: CreateChapterDto): Promise { - if (!createChapterDto.orderIndex) { - createChapterDto.orderIndex = await this.validateAndGetNextOrderIndex( - createChapterDto.moduleId, - ); - } else { - const existingChapter = await this.chapterRepository.findOne({ - where: { orderIndex: createChapterDto.orderIndex }, - }); - - if (existingChapter) { - throw new BadRequestException('Order index is duplicated'); - } - } + async validateAndGetNextOrderIndex(moduleId: string): Promise { + const existingChapter = await this.chapterRepository.find({ + where: { moduleId }, + order: { orderIndex: 'DESC' }, + }); - const chapter = this.chapterRepository.create(createChapterDto); + const nextOrderIndex = existingChapter.map((chapter) => chapter.orderIndex); + const hasDuplicates = + new Set(nextOrderIndex).size !== nextOrderIndex.length; - await this.chapterRepository.save(chapter); - return chapter; - } + if (hasDuplicates) { + throw new BadRequestException('Order index is duplicated'); + } - async reorderModules(moduleId: string): Promise { - const modulesToReorder = await this.chapterRepository.find({ - where: { moduleId }, - order: { orderIndex: 'ASC' }, - }); + return nextOrderIndex.length ? nextOrderIndex[0] + 1 : 1; + } - for (let i = 0; i < modulesToReorder.length; i++) { - modulesToReorder[i].orderIndex = i + 1; + async create(createChapterDto: CreateChapterDto): Promise { + if (!createChapterDto.orderIndex) { + createChapterDto.orderIndex = await this.validateAndGetNextOrderIndex( + createChapterDto.moduleId, + ); + } else { + const existingChapter = await this.chapterRepository.findOne({ + where: { orderIndex: createChapterDto.orderIndex }, + }); + + if (existingChapter) { + throw new BadRequestException('Order index is duplicated'); + } + } + + const chapter = this.chapterRepository.create(createChapterDto); + + await this.chapterRepository.save(chapter); + await this.chatRoomService.create({ + name: `${chapter.title} Questions`, + type: ChatRoomType.QUESTION, + chapterId: chapter.id, + status: ChatRoomStatus.ACTIVE, + }); + await this.chatRoomService.create({ + name: `${chapter.title} Discussion`, + type: ChatRoomType.DISCUSSION, + chapterId: chapter.id, + status: ChatRoomStatus.ACTIVE, + }); + return chapter; } - await this.chapterRepository.save(modulesToReorder); - } + async reorderModules(moduleId: string): Promise { + const modulesToReorder = await this.chapterRepository.find({ + where: { moduleId }, + order: { orderIndex: 'ASC' }, + }); - async update( - id: string, - updateChapterDto: UpdateChapterDto, - ): Promise { - const chapter = await this.findOne(id, { where: { id } }); + for (let i = 0; i < modulesToReorder.length; i++) { + modulesToReorder[i].orderIndex = i + 1; + } - if (!chapter) { - throw new NotFoundException('Chapter not found'); + await this.chapterRepository.save(modulesToReorder); } - if ( - updateChapterDto.orderIndex && - updateChapterDto.orderIndex !== chapter.orderIndex - ) { - const existingChapter = await this.chapterRepository.findOne({ - where: { orderIndex: updateChapterDto.orderIndex }, - }); - - if (existingChapter) { - throw new BadRequestException('Order index is duplicated'); - } + async update( + id: string, + updateChapterDto: UpdateChapterDto, + ): Promise { + const chapter = await this.findOne(id, { where: { id } }); + + if (!chapter) { + throw new NotFoundException('Chapter not found'); + } + + if ( + updateChapterDto.orderIndex && + updateChapterDto.orderIndex !== chapter.orderIndex + ) { + const existingChapter = await this.chapterRepository.findOne({ + where: { orderIndex: updateChapterDto.orderIndex }, + }); + + if (existingChapter) { + throw new BadRequestException('Order index is duplicated'); + } + } + + this.chapterRepository.merge(chapter, updateChapterDto); + await this.chapterRepository.save(chapter); + + return chapter; } - this.chapterRepository.merge(chapter, updateChapterDto); - await this.chapterRepository.save(chapter); + async remove(id: string): Promise { + const chapter = await this.findOne(id, { where: { id } }); - return chapter; - } + if (!chapter) { + throw new BadRequestException('Chapter not found'); + } - async remove(id: string): Promise { - const chapter = await this.findOne(id, { where: { id } }); + const result = await this.chapterRepository.remove(chapter); - if (!chapter) { - throw new BadRequestException('Chapter not found'); - } - - const result = await this.chapterRepository.remove(chapter); + await this.reorderModules(chapter.moduleId); - await this.reorderModules(chapter.moduleId); - - return result; - } + return result; + } } diff --git a/src/chapter/dtos/chapter-response.dto.ts b/src/chapter/dtos/chapter-response.dto.ts index 4ffe5fc..5b88151 100644 --- a/src/chapter/dtos/chapter-response.dto.ts +++ b/src/chapter/dtos/chapter-response.dto.ts @@ -4,107 +4,107 @@ import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response. import { Chapter } from '../chapter.entity'; export class ChapterResponseDto { - @ApiProperty({ - description: 'Chapter ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }) - id: string; + @ApiProperty({ + description: 'Chapter ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + id: string; - @ApiProperty({ - description: 'Chapter title', - type: String, - example: 'Introduction to Variables', - }) - title: string; + @ApiProperty({ + description: 'Chapter title', + type: String, + example: 'Introduction to Variables', + }) + title: string; - @ApiProperty({ - description: 'Chapter description', - type: String, - example: 'Learn about variables and data types in programming', - }) - description: string; + @ApiProperty({ + description: 'Chapter description', + type: String, + example: 'Learn about variables and data types in programming', + }) + description: string; - @ApiProperty({ - description: 'Chapter video URL', - type: String, - example: 'https://www.youtube.com/watch?v=8k-9mU5KfBQ', - }) - videoUrl: string; + @ApiProperty({ + description: 'Chapter video URL', + type: String, + example: 'https://www.youtube.com/watch?v=8k-9mU5KfBQ', + }) + videoUrl: string; - @ApiProperty({ - description: 'Chapter content', - type: String, - example: 'This chapter covers the basics of programming', - }) - content: string; + @ApiProperty({ + description: 'Chapter content', + type: String, + example: 'This chapter covers the basics of programming', + }) + content: string; - @ApiProperty({ - description: 'Chapter summary', - type: String, - example: 'This chapter is an introduction to programming', - }) - summary: string; + @ApiProperty({ + description: 'Chapter summary', + type: String, + example: 'This chapter is an introduction to programming', + }) + summary: string; - @ApiProperty({ - description: 'Chapter duration', - type: Number, - example: 10, - }) - duration: number; + @ApiProperty({ + description: 'Chapter duration', + type: Number, + example: 10, + }) + duration: number; - @ApiProperty({ - description: 'Chapter module', - type: CourseModuleResponseDto, - example: { - id: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - title: 'Introduction to Programming', - description: 'This module is an introduction to programming', - orderIndex: 1, - courseId: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }, - }) - module: CourseModuleResponseDto; + @ApiProperty({ + description: 'Chapter module', + type: CourseModuleResponseDto, + example: { + id: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + title: 'Introduction to Programming', + description: 'This module is an introduction to programming', + orderIndex: 1, + courseId: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }, + }) + module: CourseModuleResponseDto; - @ApiProperty({ - description: 'Chapter created date', - type: Date, - example: new Date(), - }) - createdAt: Date; + @ApiProperty({ + description: 'Chapter created date', + type: Date, + example: new Date(), + }) + createdAt: Date; - @ApiProperty({ - description: 'Chapter updated date', - type: Date, - example: new Date(), - }) - updatedAt: Date; + @ApiProperty({ + description: 'Chapter updated date', + type: Date, + example: new Date(), + }) + updatedAt: Date; - constructor(chapter: Chapter) { - this.id = chapter.id; - this.title = chapter.title; - this.description = chapter.description; - this.videoUrl = chapter.videoUrl; - this.content = chapter.content; - this.summary = chapter.summary; - this.duration = chapter.duration; - this.createdAt = chapter.createdAt; - this.updatedAt = chapter.updatedAt; - } + constructor(chapter: Chapter) { + this.id = chapter.id; + this.title = chapter.title; + this.description = chapter.description; + this.videoUrl = chapter.videoUrl; + this.content = chapter.content; + this.summary = chapter.summary; + this.duration = chapter.duration; + this.createdAt = chapter.createdAt; + this.updatedAt = chapter.updatedAt; + } } export class PaginatedChapterResponseDto extends PaginatedResponse( - ChapterResponseDto, + ChapterResponseDto, ) { - constructor( - chapters: Chapter[], - total: number, - pageSize: number, - currentPage: number, - ) { - const chapterDtos = chapters.map( - (chapter) => new ChapterResponseDto(chapter), - ); - super(chapterDtos, total, pageSize, currentPage); - } + constructor( + chapters: Chapter[], + total: number, + pageSize: number, + currentPage: number, + ) { + const chapterDtos = chapters.map( + (chapter) => new ChapterResponseDto(chapter), + ); + super(chapterDtos, total, pageSize, currentPage); + } } diff --git a/src/chat-message/chat-message.entity.ts b/src/chat-message/chat-message.entity.ts index 49e8a5c..bfc8141 100644 --- a/src/chat-message/chat-message.entity.ts +++ b/src/chat-message/chat-message.entity.ts @@ -49,6 +49,7 @@ export class ChatMessage { @ManyToOne(() => User, { nullable: false, onDelete: 'CASCADE', + eager: true, }) user: User; } \ No newline at end of file diff --git a/src/chat-room/chat-room.controller.ts b/src/chat-room/chat-room.controller.ts index 0761ffa..6b1cfa1 100644 --- a/src/chat-room/chat-room.controller.ts +++ b/src/chat-room/chat-room.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Injectable, Query, HttpStatus, Param, ParseUUIDPipe, Post, HttpCode, Body, Patch, Delete } from "@nestjs/common"; +import { Controller, Injectable, Query, HttpStatus, Param, ParseUUIDPipe, Post, HttpCode, Body, Patch, Delete, UseGuards } from "@nestjs/common"; import { Get } from "@nestjs/common"; import { ChatRoomService } from "./chat-room.service"; import { PaginateQueryDto } from "src/shared/pagination/dtos/paginate-query.dto"; @@ -8,6 +8,7 @@ import { Roles } from "src/shared/decorators/role.decorator"; import { Role } from "src/shared/enums"; import { CreateChatRoomDto } from "./dtos/create-chat-room.dto"; import { UpdateChatRoomDto } from "./dtos/update-chat-room.dto"; +import { ChatRoomOwnershipGuard } from "./guards/chat-room-ownership.guard"; @Controller('chat-room') @Injectable() @@ -57,7 +58,8 @@ export class ChatRoomController { type: String, required: true, }) - @Roles(Role.ADMIN) + @Roles(Role.ADMIN, Role.STUDENT) + @UseGuards(ChatRoomOwnershipGuard) async findById( @Param( 'id', diff --git a/src/chat-room/chat-room.module.ts b/src/chat-room/chat-room.module.ts index 83a59f7..5a44124 100644 --- a/src/chat-room/chat-room.module.ts +++ b/src/chat-room/chat-room.module.ts @@ -5,16 +5,19 @@ import { ChatRoomService } from "./chat-room.service"; import { TypeOrmModule } from "@nestjs/typeorm"; import { ChatRoom } from "./chat-room.entity"; import { chatRoomProviders } from "./chat-room.providers"; +import { EnrollmentModule } from "src/enrollment/enrollment.module"; @Module({ imports: [ DatabaseModule, TypeOrmModule.forFeature([ChatRoom]), + EnrollmentModule, ], controllers: [ChatRoomController], providers: [ ...chatRoomProviders, ChatRoomService, ], + exports: [ChatRoomService], }) export class ChatRoomModule { } \ No newline at end of file diff --git a/src/chat-room/guards/chat-room-ownership.guard.ts b/src/chat-room/guards/chat-room-ownership.guard.ts new file mode 100644 index 0000000..8886df4 --- /dev/null +++ b/src/chat-room/guards/chat-room-ownership.guard.ts @@ -0,0 +1,39 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + NotFoundException, +} from '@nestjs/common'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { ChatRoomService } from '../chat-room.service'; +import { Enrollment } from 'src/enrollment/enrollment.entity'; +import { Repository } from 'typeorm'; +import { InjectRepository } from '@nestjs/typeorm'; + +@Injectable() +export class ChatRoomOwnershipGuard implements CanActivate { + constructor( + private readonly chatRoomService: ChatRoomService, + @InjectRepository(Enrollment) + private readonly enrollmentRepository: Repository, + ) { } + + async canActivate(context: ExecutionContext): Promise { + const request: AuthenticatedRequest = context.switchToHttp().getRequest(); + const userId = request.user.id; + const chatRoomId = request.params.id; + + const chatRoom = await this.chatRoomService.findOne({ where: { id: chatRoomId } }); + const course = chatRoom.chapter.module.course; + const enrollment = await this.enrollmentRepository.findOne({ + where: { + user: { id: userId }, + course: { id: course.id }, + }, + }); + if (!enrollment) + throw new NotFoundException('You are not enrolled in this course'); + + return true; + } +} \ No newline at end of file diff --git a/src/enrollment/enrollment.controller.ts b/src/enrollment/enrollment.controller.ts index 40ceda1..d660898 100644 --- a/src/enrollment/enrollment.controller.ts +++ b/src/enrollment/enrollment.controller.ts @@ -1,24 +1,23 @@ import { - BadRequestException, - Body, - Controller, - Delete, - Get, - HttpStatus, - Injectable, - Param, - ParseUUIDPipe, - Patch, - Post, - Query, - Req, + BadRequestException, + Body, + Controller, + Delete, + Get, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, } from '@nestjs/common'; import { - ApiBearerAuth, - ApiParam, - ApiQuery, - ApiResponse, - ApiTags, + ApiBearerAuth, + ApiParam, + ApiResponse, + ApiTags, } from '@nestjs/swagger'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { Roles } from 'src/shared/decorators/role.decorator'; @@ -26,8 +25,8 @@ import { Role } from 'src/shared/enums'; import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { CreateEnrollmentDto } from './dtos/create-enrollment.dto'; import { - EnrollmentResponseDto, - PaginatedEnrollmentResponseDto, + EnrollmentResponseDto, + PaginatedEnrollmentResponseDto, } from './dtos/enrollment-response.dto'; import { UpdateEnrollmentDto } from './dtos/update-enrollment.dto'; import { EnrollmentService } from './enrollment.service'; @@ -37,90 +36,90 @@ import { EnrollmentService } from './enrollment.service'; @ApiBearerAuth() @Injectable() export class EnrollmentController { - constructor(private readonly enrollmentService: EnrollmentService) {} + constructor(private readonly enrollmentService: EnrollmentService) { } - @Get() - @ApiResponse({ - status: HttpStatus.OK, - type: EnrollmentResponseDto, - description: 'Get all enrollments', - isArray: true, - }) - async findAll( - @Query() query: PaginateQueryDto, - ): Promise { - return this.enrollmentService.findAll(query); - } + @Get() + @ApiResponse({ + status: HttpStatus.OK, + type: EnrollmentResponseDto, + description: 'Get all enrollments', + isArray: true, + }) + async findAll( + @Query() query: PaginateQueryDto, + ): Promise { + return this.enrollmentService.findAll(query); + } - @Get(':id') - @ApiParam({ - name: 'id', - type: String, - description: 'Enrollment ID', - }) - @ApiResponse({ - status: HttpStatus.OK, - type: EnrollmentResponseDto, - description: 'Get enrollment by ID', - }) - async findOne( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - ): Promise { - return this.enrollmentService.findOne(id, { where: { id } }); - } + @Get(':id') + @ApiParam({ + name: 'id', + type: String, + description: 'Enrollment ID', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: EnrollmentResponseDto, + description: 'Get enrollment by ID', + }) + async findOne( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + return this.enrollmentService.findOne(id, { where: { id } }); + } - @Post() - @Roles(Role.STUDENT) - @ApiResponse({ - status: HttpStatus.CREATED, - type: EnrollmentResponseDto, - description: 'Create enrollment', - }) - async create( - @Body() createEnrollmentDto: CreateEnrollmentDto, - @Req() req: AuthenticatedRequest, - ): Promise { - return this.enrollmentService.create(createEnrollmentDto); - } + @Post() + @Roles(Role.STUDENT) + @ApiResponse({ + status: HttpStatus.CREATED, + type: EnrollmentResponseDto, + description: 'Create enrollment', + }) + async create( + @Body() createEnrollmentDto: CreateEnrollmentDto, + @Req() req: AuthenticatedRequest, + ): Promise { + return this.enrollmentService.create(createEnrollmentDto); + } - @Patch(':id') - @Roles(Role.STUDENT) - @ApiParam({ - name: 'id', - type: String, - description: 'Enrollment ID', - }) - @ApiResponse({ - status: HttpStatus.OK, - type: EnrollmentResponseDto, - description: 'Update enrollment by ID', - }) - async update( - @Param('id', ParseUUIDPipe) id: string, - @Body() updateEnrollmentDto: UpdateEnrollmentDto, - ): Promise { - return this.enrollmentService.update(id, updateEnrollmentDto); - } + @Patch(':id') + @Roles(Role.STUDENT) + @ApiParam({ + name: 'id', + type: String, + description: 'Enrollment ID', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: EnrollmentResponseDto, + description: 'Update enrollment by ID', + }) + async update( + @Param('id', ParseUUIDPipe) id: string, + @Body() updateEnrollmentDto: UpdateEnrollmentDto, + ): Promise { + return this.enrollmentService.update(id, updateEnrollmentDto); + } - @Delete(':id') - @Roles(Role.STUDENT) - @ApiParam({ - name: 'id', - type: String, - description: 'Enrollment ID', - }) - @ApiResponse({ - status: HttpStatus.OK, - description: 'Delete enrollment by ID', - }) - async remove(@Param('id', ParseUUIDPipe) id: string): Promise { - return this.enrollmentService.remove(id); - } + @Delete(':id') + @Roles(Role.STUDENT) + @ApiParam({ + name: 'id', + type: String, + description: 'Enrollment ID', + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Delete enrollment by ID', + }) + async remove(@Param('id', ParseUUIDPipe) id: string): Promise { + return this.enrollmentService.remove(id); + } } diff --git a/src/enrollment/enrollment.module.ts b/src/enrollment/enrollment.module.ts index e6e7746..1e7579b 100644 --- a/src/enrollment/enrollment.module.ts +++ b/src/enrollment/enrollment.module.ts @@ -8,5 +8,6 @@ import { EnrollmentService } from './enrollment.service'; imports: [TypeOrmModule.forFeature([Enrollment])], controllers: [EnrollmentController], providers: [EnrollmentService], + exports: [EnrollmentService], }) export class EnrollmentModule {} diff --git a/src/enrollment/enrollment.service.ts b/src/enrollment/enrollment.service.ts index 22fd237..ed11119 100644 --- a/src/enrollment/enrollment.service.ts +++ b/src/enrollment/enrollment.service.ts @@ -1,7 +1,7 @@ import { - BadRequestException, - Injectable, - NotFoundException, + BadRequestException, + Injectable, + NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { createPagination } from 'src/shared/pagination'; @@ -14,79 +14,79 @@ import { EnrollmentStatus } from './enums/enrollment-status.enum'; @Injectable() export class EnrollmentService { - constructor( - @InjectRepository(Enrollment) - private readonly enrollmentRepository: Repository, - ) {} + constructor( + @InjectRepository(Enrollment) + private readonly enrollmentRepository: Repository, + ) { } - async findAll({ - page = 1, - limit = 20, - }: { - page?: number; - limit?: number; - }): Promise { - const { find } = await createPagination(this.enrollmentRepository, { - page, - limit, - }); + async findAll({ + page = 1, + limit = 20, + }: { + page?: number; + limit?: number; + }): Promise { + const { find } = await createPagination(this.enrollmentRepository, { + page, + limit, + }); - const enrollments = await find({ - relations: { - user: true, - course: true, - }, - }).run(); + const enrollments = await find({ + relations: { + user: true, + course: true, + }, + }).run(); - return enrollments; - } + return enrollments; + } - async findOne( - id: string, - options: FindOneOptions, - ): Promise { - const baseWhere = options.where as FindOptionsWhere; - const whereCondition = { ...baseWhere, id }; + async findOne( + id: string, + options: FindOneOptions, + ): Promise { + const baseWhere = options.where as FindOptionsWhere; + const whereCondition = { ...baseWhere, id }; - const enrollment = await this.enrollmentRepository.findOne({ - where: whereCondition, - relations: { - user: true, - course: true, - }, - }); + const enrollment = await this.enrollmentRepository.findOne({ + where: whereCondition, + relations: { + user: true, + course: true, + }, + }); - if (!enrollment) { - throw new NotFoundException('Enrollment not found'); - } + if (!enrollment) { + throw new NotFoundException('Enrollment not found'); + } - return enrollment; - } + return enrollment; + } - async create(createEnrollmentDto: CreateEnrollmentDto): Promise { - const enrollment = this.enrollmentRepository.create(createEnrollmentDto); - await this.enrollmentRepository.save(enrollment); + async create(createEnrollmentDto: CreateEnrollmentDto): Promise { + const enrollment = this.enrollmentRepository.create(createEnrollmentDto); + await this.enrollmentRepository.save(enrollment); - return enrollment; - } + return enrollment; + } - async update( - id: string, - updateEnrollmentDto: UpdateEnrollmentDto, - ): Promise { - const enrollment = await this.findOne(id, { - where: { status: EnrollmentStatus.ACTIVE }, - }); - this.enrollmentRepository.merge(enrollment, updateEnrollmentDto); - await this.enrollmentRepository.save(enrollment); + async update( + id: string, + updateEnrollmentDto: UpdateEnrollmentDto, + ): Promise { + const enrollment = await this.findOne(id, { + where: { status: EnrollmentStatus.ACTIVE }, + }); + this.enrollmentRepository.merge(enrollment, updateEnrollmentDto); + await this.enrollmentRepository.save(enrollment); - return enrollment; - } + return enrollment; + } - async remove(id: string): Promise { - const enrollment = await this.findOne(id, { - where: { id }, - }); - await this.enrollmentRepository.remove(enrollment); - } + async remove(id: string): Promise { + const enrollment = await this.findOne(id, { + where: { id }, + }); + await this.enrollmentRepository.remove(enrollment); + } } From d2ae63ba6584b46f47bf41ac1ef57dc5ae0d8742 Mon Sep 17 00:00:00 2001 From: khris-xp Date: Sun, 17 Nov 2024 21:15:14 +0700 Subject: [PATCH 071/155] feat: user background module service --- src/app.module.ts | 2 + .../user-background-topic.entity.ts | 6 + .../dtos/create-user-background.dto.ts | 29 ++++ .../dtos/update-user-background.dto.ts | 4 + .../dtos/user-background-response.dto.ts | 72 ++++++++++ .../user-background.controller.ts | 133 +++++++++++++++++ src/user-background/user-background.entity.ts | 60 ++++++++ src/user-background/user-background.module.ts | 12 ++ .../user-background.service.ts | 134 ++++++++++++++++++ src/user-occupation/user-occupation.entity.ts | 5 + src/user/user.entity.ts | 4 + 11 files changed, 461 insertions(+) create mode 100644 src/user-background/dtos/create-user-background.dto.ts create mode 100644 src/user-background/dtos/update-user-background.dto.ts create mode 100644 src/user-background/dtos/user-background-response.dto.ts create mode 100644 src/user-background/user-background.controller.ts create mode 100644 src/user-background/user-background.entity.ts create mode 100644 src/user-background/user-background.module.ts create mode 100644 src/user-background/user-background.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index a1a18cb..9c19d6a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -24,6 +24,7 @@ import { dotenvConfig } from './shared/configs/dotenv.config'; import { GLOBAL_CONFIG } from './shared/constants/global-config.constant'; import { RolesGuard } from './shared/guards/role.guard'; import { UserBackgroundTopicModule } from './user-background-topic/user-background-topic.module'; +import { UserBackgroundModule } from './user-background/user-background.module'; import { UserOccupationModule } from './user-occupation/user-occupation.module'; import { UserStreak } from './user-streak/user-streak.entity'; import { UserStreakModule } from './user-streak/user-streak.module'; @@ -70,6 +71,7 @@ const forFeatures = TypeOrmModule.forFeature([User, UserStreak]); QuestionOptionModule, UserOccupationModule, UserBackgroundTopicModule, + UserBackgroundModule, ], controllers: [AppController], providers: [ diff --git a/src/user-background-topic/user-background-topic.entity.ts b/src/user-background-topic/user-background-topic.entity.ts index c998afc..5a8ccb2 100644 --- a/src/user-background-topic/user-background-topic.entity.ts +++ b/src/user-background-topic/user-background-topic.entity.ts @@ -1,7 +1,10 @@ +import { UserBackground } from 'src/user-background/user-background.entity'; import { Column, CreateDateColumn, Entity, + ManyToMany, + OneToMany, PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; @@ -32,6 +35,9 @@ export class UserBackgroundTopic { }) level: UserBackgroundTopicLevel; + @ManyToMany(() => UserBackground, (background) => background.topics) + userBackgrounds: UserBackground[]; + @CreateDateColumn({ type: 'timestamp', name: 'created_at', diff --git a/src/user-background/dtos/create-user-background.dto.ts b/src/user-background/dtos/create-user-background.dto.ts new file mode 100644 index 0000000..3878db5 --- /dev/null +++ b/src/user-background/dtos/create-user-background.dto.ts @@ -0,0 +1,29 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsArray, IsNotEmpty, IsUUID } from 'class-validator'; + +export class CreateUserBackground { + @IsNotEmpty() + @IsUUID() + @ApiProperty({ + description: 'User ID', + type: String, + }) + userId: string; + + @IsNotEmpty() + @IsUUID() + @ApiProperty({ + description: 'Occupation ID', + type: String, + }) + occupationId: string; + + @IsArray() + @IsUUID(undefined, { each: true }) + @ApiProperty({ + description: 'Topic IDs', + type: [String], + default: [], + }) + topics: string[]; +} diff --git a/src/user-background/dtos/update-user-background.dto.ts b/src/user-background/dtos/update-user-background.dto.ts new file mode 100644 index 0000000..d0592bc --- /dev/null +++ b/src/user-background/dtos/update-user-background.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateUserBackground } from './create-user-background.dto'; + +export class UpdateUserBackground extends PartialType(CreateUserBackground) {} diff --git a/src/user-background/dtos/user-background-response.dto.ts b/src/user-background/dtos/user-background-response.dto.ts new file mode 100644 index 0000000..fc3bffa --- /dev/null +++ b/src/user-background/dtos/user-background-response.dto.ts @@ -0,0 +1,72 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { UserBackgroundTopicResponseDto } from 'src/user-background-topic/dtos/user-background-response.dto'; +import { UserOccupationResponseDto } from 'src/user-occupation/dtos/user-occupation-response.dto'; +import { UserResponseDto } from 'src/user/dtos/user-response.dto'; +import { UserBackground } from '../user-background.entity'; + +export class UserBackgroundResponseDto { + @ApiProperty({ + description: 'User background ID', + type: String, + }) + id: string; + + @ApiProperty({ + description: 'User data', + type: UserResponseDto, + }) + user: UserResponseDto; + + @ApiProperty({ + description: 'User occupation data', + type: UserOccupationResponseDto, + }) + occupation: UserOccupationResponseDto; + + @ApiProperty({ + description: 'User background topics', + type: UserBackgroundTopicResponseDto, + isArray: true, + }) + topics: UserBackgroundTopicResponseDto[]; + + @ApiProperty({ + description: 'Created date', + type: Date, + }) + createdAt: Date; + + @ApiProperty({ + description: 'Updated date', + type: Date, + }) + updatedAt: Date; + + constructor(data: UserBackground) { + this.id = data.id; + this.user = data.user; + this.occupation = data.occupation; + this.topics = data.topics; + this.createdAt = data.createdAt; + this.updatedAt = data.updatedAt; + } +} + +export class PaginatedUserBackgroundResponseDto extends PaginatedResponse( + UserBackgroundResponseDto, +) { + constructor( + userBackground: UserBackground[], + total: number, + page: number, + limit: number, + ) { + super( + userBackground.map((item) => new UserBackgroundResponseDto(item)), + total, + page, + limit, + ); + } +} diff --git a/src/user-background/user-background.controller.ts b/src/user-background/user-background.controller.ts new file mode 100644 index 0000000..9f96f48 --- /dev/null +++ b/src/user-background/user-background.controller.ts @@ -0,0 +1,133 @@ +import { + Body, + Controller, + Delete, + Get, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, +} from '@nestjs/common'; +import { + ApiBearerAuth, + ApiParam, + ApiQuery, + ApiResponse, + ApiTags, +} from '@nestjs/swagger'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { CreateUserBackground } from './dtos/create-user-background.dto'; +import { UpdateUserBackground } from './dtos/update-user-background.dto'; +import { + PaginatedUserBackgroundResponseDto, + UserBackgroundResponseDto, +} from './dtos/user-background-response.dto'; +import { UserBackgroundService } from './user-background.service'; + +@Controller('user-background') +@ApiTags('User Background') +@ApiBearerAuth() +@Injectable() +export class UserBackgroundController { + constructor(private readonly userBackgroundService: UserBackgroundService) {} + + @Get() + @ApiResponse({ + status: HttpStatus.OK, + type: UserBackgroundResponseDto, + description: 'Get all user backgrounds', + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @Roles(Role.ADMIN) + async findAll( + @Query() query: PaginateQueryDto, + ): Promise { + return this.userBackgroundService.findAll(query); + } + + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + type: UserBackgroundResponseDto, + description: 'Get user background by ID', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'User background ID', + }) + @Roles(Role.ADMIN) + async findOne( + @Param('id', ParseUUIDPipe) id: string, + ): Promise { + return this.userBackgroundService.findOne(id, { where: { id } }); + } + + @Post() + @ApiResponse({ + status: HttpStatus.CREATED, + type: UserBackgroundResponseDto, + description: 'Create a user background', + }) + @Roles(Role.ADMIN) + async create( + @Body() data: CreateUserBackground, + ): Promise { + return this.userBackgroundService.create(data); + } + + @Patch(':id') + @ApiResponse({ + status: HttpStatus.OK, + type: UserBackgroundResponseDto, + description: 'Update user background by ID', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'User background ID', + }) + @Roles(Role.ADMIN) + async update( + @Param('id', ParseUUIDPipe) id: string, + @Body() data: UpdateUserBackground, + ): Promise { + return this.userBackgroundService.update(id, data); + } + + @Delete(':id') + @ApiResponse({ + status: HttpStatus.OK, + type: UserBackgroundResponseDto, + description: 'Delete user background by ID', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'User background ID', + }) + @Roles(Role.ADMIN) + async remove( + @Param('id', new ParseUUIDPipe()) id: string, + ): Promise { + return this.userBackgroundService.remove(id); + } +} diff --git a/src/user-background/user-background.entity.ts b/src/user-background/user-background.entity.ts new file mode 100644 index 0000000..344254d --- /dev/null +++ b/src/user-background/user-background.entity.ts @@ -0,0 +1,60 @@ +import { UserBackgroundTopic } from 'src/user-background-topic/user-background-topic.entity'; +import { UserOccupation } from 'src/user-occupation/user-occupation.entity'; +import { User } from 'src/user/user.entity'; +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + JoinTable, + ManyToMany, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +@Entity() +export class UserBackground { + @PrimaryGeneratedColumn('uuid') + id: string; + + @ManyToOne(() => User, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'user_id' }) + user: User; + + @Column({ name: 'user_id' }) + userId: string; + + @ManyToMany(() => UserBackgroundTopic, (topic) => topic.userBackgrounds) + @JoinTable({ + name: 'user_background_topics_mapping', + joinColumn: { + name: 'user_background_id', + referencedColumnName: 'id', + }, + inverseJoinColumn: { + name: 'topic_id', + referencedColumnName: 'id', + }, + }) + topics: UserBackgroundTopic[]; + + @ManyToOne(() => UserOccupation, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'occupation_id' }) + occupation: UserOccupation; + + @Column({ name: 'occupation_id' }) + occupationId: string; + + @CreateDateColumn({ + type: 'timestamp', + name: 'created_at', + }) + createdAt: Date; + + @UpdateDateColumn({ + type: 'timestamp', + name: 'updated_at', + }) + updatedAt: Date; +} diff --git a/src/user-background/user-background.module.ts b/src/user-background/user-background.module.ts new file mode 100644 index 0000000..d3b483b --- /dev/null +++ b/src/user-background/user-background.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { UserBackgroundController } from './user-background.controller'; +import { UserBackground } from './user-background.entity'; +import { UserBackgroundService } from './user-background.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([UserBackground])], + controllers: [UserBackgroundController], + providers: [UserBackgroundService], +}) +export class UserBackgroundModule {} diff --git a/src/user-background/user-background.service.ts b/src/user-background/user-background.service.ts new file mode 100644 index 0000000..0bb0547 --- /dev/null +++ b/src/user-background/user-background.service.ts @@ -0,0 +1,134 @@ +import { + BadRequestException, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { createPagination } from 'src/shared/pagination'; +import { UserBackgroundTopic } from 'src/user-background-topic/user-background-topic.entity'; +import { + DeepPartial, + FindOneOptions, + FindOptionsWhere, + Repository, +} from 'typeorm'; +import { CreateUserBackground } from './dtos/create-user-background.dto'; +import { UpdateUserBackground } from './dtos/update-user-background.dto'; +import { + PaginatedUserBackgroundResponseDto, + UserBackgroundResponseDto, +} from './dtos/user-background-response.dto'; +import { UserBackground } from './user-background.entity'; + +@Injectable() +export class UserBackgroundService { + constructor( + @InjectRepository(UserBackground) + private readonly userBackgroundRepository: Repository, + private readonly userBackgroundTopicRepository: Repository, + ) {} + + async findAll({ + page = 1, + limit = 20, + }: { + page?: number; + limit?: number; + }): Promise { + const { find } = await createPagination(this.userBackgroundRepository, { + page, + limit, + }); + + const userBackground = await find({ + relations: { + user: true, + occupation: true, + topics: true, + }, + }).run(); + + return userBackground; + } + + async findOne( + id: string, + options: FindOneOptions, + ): Promise { + const baseWhere = options.where as FindOptionsWhere; + const whereCondition = { ...baseWhere, id }; + + const userBackground = await this.userBackgroundRepository.findOne({ + where: whereCondition, + relations: { + user: true, + occupation: true, + topics: true, + }, + }); + + if (!userBackground) { + throw new NotFoundException('User background not found'); + } + + return userBackground; + } + + async create(data: CreateUserBackground): Promise { + const topics = await this.userBackgroundTopicRepository.findByIds( + data.topics, + ); + + const userBackground = this.userBackgroundRepository.create({ + userId: data.userId, + occupationId: data.occupationId, + topics: topics, + }); + + const savedUserBackground = await this.userBackgroundRepository.save( + userBackground, + ); + return savedUserBackground; + } + + async update( + id: string, + data: UpdateUserBackground, + ): Promise { + const userBackground = await this.userBackgroundRepository.findOne({ + where: { id }, + }); + + const updateData: DeepPartial = { + userId: data.userId, + occupationId: data.occupationId, + }; + + if (data.topics) { + const topics = await this.userBackgroundTopicRepository.findByIds( + data.topics, + ); + if (topics.length !== data.topics.length) { + throw new BadRequestException('Some topics were not found'); + } + updateData.topics = topics; + } + + this.userBackgroundRepository.merge(userBackground, updateData); + const savedUserBackground = await this.userBackgroundRepository.save( + userBackground, + ); + + return savedUserBackground; + } + + async remove(id: string): Promise { + const userBackground = await this.findOne(id, { + where: { id }, + }); + + await this.userBackgroundRepository.remove(userBackground); + + return userBackground; + } +} diff --git a/src/user-occupation/user-occupation.entity.ts b/src/user-occupation/user-occupation.entity.ts index 1139ac5..125266e 100644 --- a/src/user-occupation/user-occupation.entity.ts +++ b/src/user-occupation/user-occupation.entity.ts @@ -1,7 +1,9 @@ +import { UserBackground } from 'src/user-background/user-background.entity'; import { Column, CreateDateColumn, Entity, + OneToMany, PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; @@ -23,6 +25,9 @@ export class UserOccupation { }) description: string; + @OneToMany(() => UserBackground, (background) => background.occupation) + backgrounds: UserBackground[]; + @CreateDateColumn({ name: 'created_at', type: 'timestamp', diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index d7a4cc9..f0a2fbd 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -2,6 +2,7 @@ import { Course } from 'src/course/course.entity'; import { Enrollment } from 'src/enrollment/enrollment.entity'; import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; import { Role } from 'src/shared/enums/roles.enum'; +import { UserBackground } from 'src/user-background/user-background.entity'; import { Column, CreateDateColumn, @@ -34,6 +35,9 @@ export class User { }) role: Role; + @OneToMany(() => UserBackground, (background) => background.user) + backgrounds: UserBackground[]; + @Column({ nullable: false, unique: true, From 9b14a7debd0a82bd0df36bc2277de00f752de8de Mon Sep 17 00:00:00 2001 From: khris-xp Date: Sun, 17 Nov 2024 21:21:38 +0700 Subject: [PATCH 072/155] fix: database configs and user occupation --- src/shared/configs/database.config.ts | 2 ++ src/user-occupation/dtos/user-occupation-response.dto.ts | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index 6faa123..982e3bf 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -11,6 +11,7 @@ import { Progress } from 'src/progress/progress.entity'; import { QuestionOption } from 'src/question-option/question-option.entity'; import { Question } from 'src/question/question.entity'; import { UserBackgroundTopic } from 'src/user-background-topic/user-background-topic.entity'; +import { UserBackground } from 'src/user-background/user-background.entity'; import { UserOccupation } from 'src/user-occupation/user-occupation.entity'; import { UserStreak } from 'src/user-streak/user-streak.entity'; import { User } from 'src/user/user.entity'; @@ -42,6 +43,7 @@ export const databaseConfig: DataSourceOptions = { QuestionOption, UserOccupation, UserBackgroundTopic, + UserBackground, ], }; diff --git a/src/user-occupation/dtos/user-occupation-response.dto.ts b/src/user-occupation/dtos/user-occupation-response.dto.ts index 05c1391..0bd3d5f 100644 --- a/src/user-occupation/dtos/user-occupation-response.dto.ts +++ b/src/user-occupation/dtos/user-occupation-response.dto.ts @@ -46,14 +46,15 @@ export class PaginatedUserOccupationResponseDto extends PaginatedResponse( UserOccupationResponseDto, ) { constructor( - userOccupations: [UserOccupationResponseDto], + userOccupations: UserOccupation[], total: number, page: number, limit: number, ) { super( userOccupations.map( - (userOccupation) => new UserOccupationResponseDto(userOccupation), + (userOccupation: UserOccupation) => + new UserOccupationResponseDto(userOccupation), ), total, page, From 8ae49275511396f9ef6317c3645bac654aca2ceb Mon Sep 17 00:00:00 2001 From: Potsawee Date: Sun, 17 Nov 2024 21:23:29 +0700 Subject: [PATCH 073/155] edit status enum and path find one --- .../dtos/update-status-user-reward.dto.ts | 12 +++--- .../dtos/user-reward-response.dto.ts | 8 ++-- ...tus.enum.ts => user-reward-status.enum.ts} | 2 +- src/userReward/user-reward.controllers.ts | 40 +++++++++---------- src/userReward/user-reward.entity.ts | 6 +-- src/userReward/user-reward.service.ts | 5 +-- 6 files changed, 36 insertions(+), 37 deletions(-) rename src/userReward/enums/{status.enum.ts => user-reward-status.enum.ts} (70%) diff --git a/src/userReward/dtos/update-status-user-reward.dto.ts b/src/userReward/dtos/update-status-user-reward.dto.ts index c53799b..d384e7f 100644 --- a/src/userReward/dtos/update-status-user-reward.dto.ts +++ b/src/userReward/dtos/update-status-user-reward.dto.ts @@ -1,17 +1,17 @@ import { ApiProperty } from '@nestjs/swagger'; -import { Status } from '../enums/status.enum'; +import { UserRewardStatus } from '../enums/user-reward-status.enum'; import { IsEnum, IsNotEmpty } from 'class-validator'; export class UpdateStatusUserReward { @ApiProperty({ description: 'status of user-reward', type: String, - example: Status.EXPIRED, - enum: Status, + example: UserRewardStatus.EXPIRED, + enum: UserRewardStatus, }) @IsNotEmpty() - @IsEnum(Status, { - message: `status should be either ${Status.DELIVERED} ${Status.EXPIRED} or ${Status.PENDING}`, + @IsEnum(UserRewardStatus, { + message: `status should be either ${UserRewardStatus.DELIVERED} ${UserRewardStatus.EXPIRED} or ${UserRewardStatus.PENDING}`, }) - status: Status; + status: UserRewardStatus; } diff --git a/src/userReward/dtos/user-reward-response.dto.ts b/src/userReward/dtos/user-reward-response.dto.ts index 9ac2688..914e033 100644 --- a/src/userReward/dtos/user-reward-response.dto.ts +++ b/src/userReward/dtos/user-reward-response.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; -import { Status } from '../enums/status.enum'; import { UserReward } from '../user-reward.entity'; +import { UserRewardStatus } from '../enums/user-reward-status.enum'; export class UserRewardResponseDto { @ApiProperty({ @@ -34,10 +34,10 @@ export class UserRewardResponseDto { @ApiProperty({ description: 'status of reward', type: String, - enum: Status, - example: Status.DELIVERED, + enum: UserRewardStatus, + example: UserRewardStatus.DELIVERED, }) - status: Status; + status: UserRewardStatus; @ApiProperty({ description: 'redeem date', diff --git a/src/userReward/enums/status.enum.ts b/src/userReward/enums/user-reward-status.enum.ts similarity index 70% rename from src/userReward/enums/status.enum.ts rename to src/userReward/enums/user-reward-status.enum.ts index 8c38fce..db2635b 100644 --- a/src/userReward/enums/status.enum.ts +++ b/src/userReward/enums/user-reward-status.enum.ts @@ -1,4 +1,4 @@ -export enum Status { +export enum UserRewardStatus { PENDING = 'pending', DELIVERED = 'delivered', EXPIRED = 'expired', diff --git a/src/userReward/user-reward.controllers.ts b/src/userReward/user-reward.controllers.ts index 86c8c52..bdb64d0 100644 --- a/src/userReward/user-reward.controllers.ts +++ b/src/userReward/user-reward.controllers.ts @@ -18,7 +18,7 @@ import { UserRewardService } from './user-reward.service'; import { ApiBearerAuth, ApiResponse, ApiTags } from '@nestjs/swagger'; import { UserRewardResponseDto } from './dtos/user-reward-response.dto'; import { UpdateStatusUserReward } from './dtos/update-status-user-reward.dto'; -import { Status } from './enums/status.enum'; +import { UserRewardStatus } from './enums/user-reward-status.enum'; @Controller('user-reward') @Injectable() @@ -65,7 +65,24 @@ export class UserRewardController { return userReward; } - @Get('findOne/:id') + @Get('user') + @Roles(Role.STUDENT) + @ApiResponse({ + status: HttpStatus.OK, + type: UserRewardResponseDto, + isArray: true, + description: "get all user reward by user's id", + }) + async findByUser( + @Req() request: AuthenticatedRequest, + ): Promise { + const userRewards = await this.userRewardService.findByUser( + request.user.id, + ); + return userRewards; + } + + @Get(':id') @Roles(Role.STUDENT) @ApiResponse({ status: HttpStatus.OK, @@ -86,29 +103,12 @@ export class UserRewardController { return userReward; } - @Get('user') - @Roles(Role.STUDENT) - @ApiResponse({ - status: HttpStatus.OK, - type: UserRewardResponseDto, - isArray: true, - description: "get all user reward by user's id", - }) - async findByUser( - @Req() request: AuthenticatedRequest, - ): Promise { - const userRewards = await this.userRewardService.findByUser( - request.user.id, - ); - return userRewards; - } - @Patch(':id') @Roles(Role.STUDENT) @ApiResponse({ status: HttpStatus.OK, type: UserRewardResponseDto, - description: `update status (${Status.PENDING} ${Status.DELIVERED} or ${Status.EXPIRED})`, + description: `update status (${UserRewardStatus.PENDING} ${UserRewardStatus.DELIVERED} or ${UserRewardStatus.EXPIRED})`, }) async updateStatus( @Param( diff --git a/src/userReward/user-reward.entity.ts b/src/userReward/user-reward.entity.ts index a6f2e2a..afaddc0 100644 --- a/src/userReward/user-reward.entity.ts +++ b/src/userReward/user-reward.entity.ts @@ -6,7 +6,7 @@ import { UpdateDateColumn, ManyToOne, } from 'typeorm'; -import { Status } from './enums/status.enum'; +import { UserRewardStatus } from './enums/user-reward-status.enum'; import { User } from 'src/user/user.entity'; import { Reward } from 'src/reward/reward.entity'; @@ -33,9 +33,9 @@ export class UserReward { @Column({ nullable: false, type: 'enum', - enum: Status, + enum: UserRewardStatus, }) - status: Status; + status: UserRewardStatus; //redeemed at @CreateDateColumn({ diff --git a/src/userReward/user-reward.service.ts b/src/userReward/user-reward.service.ts index b769be3..bc9f7e6 100644 --- a/src/userReward/user-reward.service.ts +++ b/src/userReward/user-reward.service.ts @@ -9,9 +9,8 @@ import { UserReward } from './user-reward.entity'; import { User } from 'src/user/user.entity'; import { Reward } from 'src/reward/reward.entity'; import { Status } from 'src/reward/enums/status.enum'; -import { Status as rewardStatus } from './enums/status.enum'; +import { UserRewardStatus } from './enums/user-reward-status.enum'; import { UpdateStatusUserReward } from './dtos/update-status-user-reward.dto'; -import { error } from 'console'; @Injectable() export class UserRewardService { @@ -48,7 +47,7 @@ export class UserRewardService { newUserReward.user = user; newUserReward.reward = reward; newUserReward.pointsSpent = reward.points; - newUserReward.status = rewardStatus.PENDING; + newUserReward.status = UserRewardStatus.PENDING; const userRewardRes = await this.userRewardRepository.save(newUserReward); return userRewardRes; } From 2361e9942eb4c2bb31a2ceb0dad687a037fbc56c Mon Sep 17 00:00:00 2001 From: ganthepro Date: Sun, 17 Nov 2024 21:23:53 +0700 Subject: [PATCH 074/155] feat: refactor enrollment service and controller; simplify findOne method and update response handling --- .../guards/chat-room-ownership.guard.ts | 13 +++------ src/enrollment/enrollment.controller.ts | 12 ++++---- src/enrollment/enrollment.service.ts | 29 ++++++++----------- 3 files changed, 23 insertions(+), 31 deletions(-) diff --git a/src/chat-room/guards/chat-room-ownership.guard.ts b/src/chat-room/guards/chat-room-ownership.guard.ts index 8886df4..6f7554a 100644 --- a/src/chat-room/guards/chat-room-ownership.guard.ts +++ b/src/chat-room/guards/chat-room-ownership.guard.ts @@ -6,16 +6,13 @@ import { } from '@nestjs/common'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { ChatRoomService } from '../chat-room.service'; -import { Enrollment } from 'src/enrollment/enrollment.entity'; -import { Repository } from 'typeorm'; -import { InjectRepository } from '@nestjs/typeorm'; +import { EnrollmentService } from 'src/enrollment/enrollment.service'; @Injectable() export class ChatRoomOwnershipGuard implements CanActivate { constructor( private readonly chatRoomService: ChatRoomService, - @InjectRepository(Enrollment) - private readonly enrollmentRepository: Repository, + private readonly enrollmentRepository: EnrollmentService, ) { } async canActivate(context: ExecutionContext): Promise { @@ -26,10 +23,8 @@ export class ChatRoomOwnershipGuard implements CanActivate { const chatRoom = await this.chatRoomService.findOne({ where: { id: chatRoomId } }); const course = chatRoom.chapter.module.course; const enrollment = await this.enrollmentRepository.findOne({ - where: { - user: { id: userId }, - course: { id: course.id }, - }, + user: { id: userId }, + course: { id: course.id }, }); if (!enrollment) throw new NotFoundException('You are not enrolled in this course'); diff --git a/src/enrollment/enrollment.controller.ts b/src/enrollment/enrollment.controller.ts index d660898..3bea119 100644 --- a/src/enrollment/enrollment.controller.ts +++ b/src/enrollment/enrollment.controller.ts @@ -4,6 +4,7 @@ import { Controller, Delete, Get, + HttpCode, HttpStatus, Injectable, Param, @@ -72,7 +73,7 @@ export class EnrollmentController { ) id: string, ): Promise { - return this.enrollmentService.findOne(id, { where: { id } }); + return await this.enrollmentService.findOne({ id }); } @Post() @@ -86,7 +87,7 @@ export class EnrollmentController { @Body() createEnrollmentDto: CreateEnrollmentDto, @Req() req: AuthenticatedRequest, ): Promise { - return this.enrollmentService.create(createEnrollmentDto); + return await this.enrollmentService.create(createEnrollmentDto); } @Patch(':id') @@ -105,7 +106,7 @@ export class EnrollmentController { @Param('id', ParseUUIDPipe) id: string, @Body() updateEnrollmentDto: UpdateEnrollmentDto, ): Promise { - return this.enrollmentService.update(id, updateEnrollmentDto); + return await this.enrollmentService.update(id, updateEnrollmentDto); } @Delete(':id') @@ -116,10 +117,11 @@ export class EnrollmentController { description: 'Enrollment ID', }) @ApiResponse({ - status: HttpStatus.OK, + status: HttpStatus.NO_CONTENT, description: 'Delete enrollment by ID', }) + @HttpCode(HttpStatus.NO_CONTENT) async remove(@Param('id', ParseUUIDPipe) id: string): Promise { - return this.enrollmentService.remove(id); + return await this.enrollmentService.remove(id); } } diff --git a/src/enrollment/enrollment.service.ts b/src/enrollment/enrollment.service.ts index ed11119..cf0fdc3 100644 --- a/src/enrollment/enrollment.service.ts +++ b/src/enrollment/enrollment.service.ts @@ -42,23 +42,19 @@ export class EnrollmentService { } async findOne( - id: string, - options: FindOneOptions, + where: FindOptionsWhere, ): Promise { - const baseWhere = options.where as FindOptionsWhere; - const whereCondition = { ...baseWhere, id }; - - const enrollment = await this.enrollmentRepository.findOne({ - where: whereCondition, - relations: { + const options: FindOneOptions = { + where, relations: { user: true, course: true, - }, - }); + } + }; + + const enrollment = await this.enrollmentRepository.findOne(options); - if (!enrollment) { + if (!enrollment) throw new NotFoundException('Enrollment not found'); - } return enrollment; } @@ -74,18 +70,17 @@ export class EnrollmentService { id: string, updateEnrollmentDto: UpdateEnrollmentDto, ): Promise { - const enrollment = await this.findOne(id, { - where: { status: EnrollmentStatus.ACTIVE }, + const enrollment = await this.findOne({ + status: EnrollmentStatus.ACTIVE, id, }); this.enrollmentRepository.merge(enrollment, updateEnrollmentDto); await this.enrollmentRepository.save(enrollment); - return enrollment; } async remove(id: string): Promise { - const enrollment = await this.findOne(id, { - where: { id }, + const enrollment = await this.findOne({ + id, }); await this.enrollmentRepository.remove(enrollment); } From 81d0f6ed9785eb2b3b4f00ec531b3d483fad8e44 Mon Sep 17 00:00:00 2001 From: khris-xp Date: Sun, 17 Nov 2024 21:39:01 +0700 Subject: [PATCH 075/155] fix: database configs and user background topic module --- src/user-background/user-background.module.ts | 8 ++++++-- src/user-background/user-background.service.ts | 16 ---------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/src/user-background/user-background.module.ts b/src/user-background/user-background.module.ts index d3b483b..988ae90 100644 --- a/src/user-background/user-background.module.ts +++ b/src/user-background/user-background.module.ts @@ -1,12 +1,16 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { UserBackgroundTopicModule } from 'src/user-background-topic/user-background-topic.module'; import { UserBackgroundController } from './user-background.controller'; import { UserBackground } from './user-background.entity'; import { UserBackgroundService } from './user-background.service'; - @Module({ - imports: [TypeOrmModule.forFeature([UserBackground])], + imports: [ + TypeOrmModule.forFeature([UserBackground]), + UserBackgroundTopicModule, + ], controllers: [UserBackgroundController], providers: [UserBackgroundService], + exports: [UserBackgroundService], }) export class UserBackgroundModule {} diff --git a/src/user-background/user-background.service.ts b/src/user-background/user-background.service.ts index 0bb0547..4d77444 100644 --- a/src/user-background/user-background.service.ts +++ b/src/user-background/user-background.service.ts @@ -25,7 +25,6 @@ export class UserBackgroundService { constructor( @InjectRepository(UserBackground) private readonly userBackgroundRepository: Repository, - private readonly userBackgroundTopicRepository: Repository, ) {} async findAll({ @@ -75,14 +74,9 @@ export class UserBackgroundService { } async create(data: CreateUserBackground): Promise { - const topics = await this.userBackgroundTopicRepository.findByIds( - data.topics, - ); - const userBackground = this.userBackgroundRepository.create({ userId: data.userId, occupationId: data.occupationId, - topics: topics, }); const savedUserBackground = await this.userBackgroundRepository.save( @@ -104,16 +98,6 @@ export class UserBackgroundService { occupationId: data.occupationId, }; - if (data.topics) { - const topics = await this.userBackgroundTopicRepository.findByIds( - data.topics, - ); - if (topics.length !== data.topics.length) { - throw new BadRequestException('Some topics were not found'); - } - updateData.topics = topics; - } - this.userBackgroundRepository.merge(userBackground, updateData); const savedUserBackground = await this.userBackgroundRepository.save( userBackground, From 8a2fe3b1e9db65d984d85a50e2cd30b00e57bf5a Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Sun, 17 Nov 2024 22:03:41 +0700 Subject: [PATCH 076/155] feat: change type in input --- src/exam-answer/exam-answer.controller.ts | 42 +++++++---- src/exam-answer/exam-answer.service.ts | 63 ++++++++++------ src/exam-attempt/exam-attempt.controller.ts | 3 +- src/exam-attempt/exam-attempt.service.dto.ts | 9 ++- src/exam/exam.controller.ts | 20 +++-- src/exam/exam.service.ts | 59 ++++++++++----- .../question-option.controller.ts | 39 ++++++---- .../question-option.service.ts | 73 +++++++++++++++---- src/question/question.controller.ts | 51 ++++++++----- src/question/question.service.ts | 67 ++++++++++++----- 10 files changed, 295 insertions(+), 131 deletions(-) diff --git a/src/exam-answer/exam-answer.controller.ts b/src/exam-answer/exam-answer.controller.ts index 2331133..d9dbca5 100644 --- a/src/exam-answer/exam-answer.controller.ts +++ b/src/exam-answer/exam-answer.controller.ts @@ -64,11 +64,15 @@ export class ExamAnswerController { @Req() request: AuthenticatedRequest, @Query() query: PaginateQueryDto, ): Promise { - return await this.examAnswerService.findAll(request, { - page: query.page, - limit: query.limit, - search: query.search, - }); + return await this.examAnswerService.findAll( + request.user.id, + request.user.role, + { + page: query.page, + limit: query.limit, + search: query.search, + }, + ); } @Get(':id') @@ -90,9 +94,13 @@ export class ExamAnswerController { ) id: string, ): Promise { - const examAnswer = await this.examAnswerService.findOne(request, { - where: { id }, - }); + const examAnswer = await this.examAnswerService.findOne( + request.user.id, + request.user.role, + { + where: { id }, + }, + ); return new ExamAnswerResponseDto(examAnswer); } @@ -136,7 +144,8 @@ export class ExamAnswerController { questionId: string, ): Promise { return await this.examAnswerService.findExamAnswerByQuestionId( - request, + request.user.id, + request.user.role, questionId, { page: query.page, @@ -186,7 +195,8 @@ export class ExamAnswerController { selectedOptionId: string, ): Promise { return await this.examAnswerService.findExamAnswerBySelectedOptionId( - request, + request.user.id, + request.user.role, selectedOptionId, { page: query.page, @@ -232,7 +242,8 @@ export class ExamAnswerController { @Body() updateExamAnswerDto: UpdateExamAnswerDto, ): Promise { const examAnswer = await this.examAnswerService.updateExamAnswer( - request, + request.user.id, + request.user.role, id, updateExamAnswerDto, ); @@ -257,8 +268,11 @@ export class ExamAnswerController { }), ) id: string, - ): Promise<{ massage: string }> { - await this.examAnswerService.deleteExamAnswer(request, id); - return { massage: 'Exam answer deleted successfully' }; + ): Promise { + await this.examAnswerService.deleteExamAnswer( + request.user.id, + request.user.role, + id, + ); } } diff --git a/src/exam-answer/exam-answer.service.ts b/src/exam-answer/exam-answer.service.ts index a1fec4e..7386274 100644 --- a/src/exam-answer/exam-answer.service.ts +++ b/src/exam-answer/exam-answer.service.ts @@ -38,7 +38,8 @@ export class ExamAnswerService { ) {} async findAll( - request: AuthenticatedRequest, + userId: string, + role: Role, { page = 1, limit = 20, @@ -54,7 +55,11 @@ export class ExamAnswerService { limit, }); - const whereCondition = this.validateAndCreateCondition(request, search); + const whereCondition = this.validateAndCreateCondition( + userId, + role, + search, + ); const exam = await find({ where: whereCondition, relations: ['examAttempt', 'question', 'selectedOption'], @@ -69,12 +74,13 @@ export class ExamAnswerService { } private validateAndCreateCondition( - request: AuthenticatedRequest, + userId: string, + role: Role, search: string, ): FindOptionsWhere | FindOptionsWhere[] { const baseSearch = search ? { answerText: ILike(`%${search}%`) } : {}; - if (request.user.role === Role.TEACHER) { + if (role === Role.TEACHER) { return [ { ...baseSearch, @@ -83,7 +89,7 @@ export class ExamAnswerService { courseModule: { course: { teacher: { - id: request.user.id, + id: userId, }, }, }, @@ -93,7 +99,7 @@ export class ExamAnswerService { ]; } - if (request.user.role === Role.ADMIN) { + if (role === Role.ADMIN) { return { ...baseSearch }; } @@ -105,7 +111,7 @@ export class ExamAnswerService { courseModule: { course: { teacher: { - id: request.user.id, + id: userId, }, }, }, @@ -116,10 +122,11 @@ export class ExamAnswerService { } async findOne( - request: AuthenticatedRequest, + userId: string, + role: Role, options: FindOneOptions = {}, ): Promise { - const whereCondition = this.validateAndCreateCondition(request, ''); + const whereCondition = this.validateAndCreateCondition(userId, role, ''); const where = Array.isArray(whereCondition) ? [ @@ -147,7 +154,8 @@ export class ExamAnswerService { } async findExamAnswerByQuestionId( - request: AuthenticatedRequest, + userId: string, + role: Role, questionId: string, { page = 1, @@ -164,7 +172,11 @@ export class ExamAnswerService { limit, }); - const whereCondition = this.validateAndCreateCondition(request, search); + const whereCondition = this.validateAndCreateCondition( + userId, + role, + search, + ); whereCondition['question'] = { id: questionId }; const question = await this.questionRepository.findOne({ @@ -187,7 +199,8 @@ export class ExamAnswerService { } async findExamAnswerBySelectedOptionId( - request: AuthenticatedRequest, + userId: string, + role: Role, selectedOptionId: string, { page = 1, @@ -204,7 +217,11 @@ export class ExamAnswerService { limit, }); - const whereCondition = this.validateAndCreateCondition(request, search); + const whereCondition = this.validateAndCreateCondition( + userId, + role, + search, + ); whereCondition['selectedOption'] = { id: selectedOptionId }; const selectedOption = await this.questionOptionRepository.findOne({ @@ -273,14 +290,19 @@ export class ExamAnswerService { } async updateExamAnswer( - request: AuthenticatedRequest, + userId: string, + role: Role, id: string, updateExamAnswerDto: UpdateExamAnswerDto, ): Promise { - const examAnswerInData = await this.findOne(request, { where: { id } }); + const examAnswerInData = await this.findOne(userId, role, { + where: { id }, + }); if (!examAnswerInData) throw new NotFoundException('Exam answer not found'); let examAttempt = null; + let selectedOption = null; + let question = null; if (updateExamAnswerDto.examAttemptId) { examAttempt = await this.examAttemptRepository.findOne({ where: { id: updateExamAnswerDto.examAttemptId }, @@ -290,10 +312,6 @@ export class ExamAnswerService { throw new NotFoundException('Exam attempt not found'); } } - - let selectedOption = null; - let question = null; - if (updateExamAnswerDto.selectedOptionId) { selectedOption = await this.questionOptionRepository.findOne({ where: { id: updateExamAnswerDto.selectedOptionId }, @@ -324,7 +342,7 @@ export class ExamAnswerService { id, updateExamAnswer, ); - if (!examAnswer) throw new NotFoundException("Can't update exam answer"); + if (!examAnswer) throw new BadRequestException("Can't update exam answer"); return await this.examAnswerRepository.findOne({ where: { id }, relations: ['examAttempt', 'question', 'selectedOption'], @@ -337,11 +355,12 @@ export class ExamAnswerService { } async deleteExamAnswer( - request: AuthenticatedRequest, + userId: string, + role: Role, id: string, ): Promise { try { - if (await this.findOne(request, { where: { id } })) { + if (await this.findOne(userId, role, { where: { id } })) { await this.examAnswerRepository.delete(id); } } catch (error) { diff --git a/src/exam-attempt/exam-attempt.controller.ts b/src/exam-attempt/exam-attempt.controller.ts index c978cea..d830846 100644 --- a/src/exam-attempt/exam-attempt.controller.ts +++ b/src/exam-attempt/exam-attempt.controller.ts @@ -191,12 +191,11 @@ export class ExamAttemptController { }), ) id: string, - ): Promise<{ massage: string }> { + ): Promise { await this.examAttemptService.deleteExamAttempt( request.user.id, request.user.role, id, ); - return { massage: 'Exam-attempt deleted successfully' }; } } diff --git a/src/exam-attempt/exam-attempt.service.dto.ts b/src/exam-attempt/exam-attempt.service.dto.ts index 719d37b..6f7d7d3 100644 --- a/src/exam-attempt/exam-attempt.service.dto.ts +++ b/src/exam-attempt/exam-attempt.service.dto.ts @@ -3,6 +3,7 @@ import { Inject, NotFoundException, ForbiddenException, + BadRequestException, } from '@nestjs/common'; import { FindOneOptions, @@ -200,7 +201,8 @@ export class ExamAttemptService { id, updateExamAttemptDto, ); - if (!examAttempt) throw new NotFoundException("Can't update exam-attempt"); + if (!examAttempt) + throw new BadRequestException("Can't update exam-attempt"); return await this.examAttemptRepository.findOne({ where: { id }, relations: ['exam', 'user'], @@ -217,9 +219,8 @@ export class ExamAttemptService { id: string, ): Promise { try { - if (await this.findOne(userId, role, { where: { id } })) { - await this.examAttemptRepository.delete(id); - } + const examAttempt = await this.findOne(userId, role, { where: { id } }); + await this.examAttemptRepository.delete(id); } catch (error) { if (error instanceof Error) throw new NotFoundException('Exam-attempt not found'); diff --git a/src/exam/exam.controller.ts b/src/exam/exam.controller.ts index 6fc3817..f024de6 100644 --- a/src/exam/exam.controller.ts +++ b/src/exam/exam.controller.ts @@ -69,7 +69,7 @@ export class ExamController { @Req() request: AuthenticatedRequest, @Query() query: PaginateQueryDto, ): Promise { - return await this.examService.findAll(request, { + return await this.examService.findAll(request.user.id, request.user.role, { page: query.page, limit: query.limit, search: query.search, @@ -93,7 +93,11 @@ export class ExamController { ) id: string, ): Promise { - const exam = await this.examService.findOne(request, { where: { id } }); + const exam = await this.examService.findOne( + request.user.id, + request.user.role, + { where: { id } }, + ); return new ExamResponseDto(exam); } @@ -131,7 +135,12 @@ export class ExamController { id: string, @Body() updateExamDto: UpdateExamDto, ): Promise { - const exam = await this.examService.updateExam(request, id, updateExamDto); + const exam = await this.examService.updateExam( + request.user.id, + request.user.role, + id, + updateExamDto, + ); return new ExamResponseDto(exam); } @@ -153,8 +162,7 @@ export class ExamController { }), ) id: string, - ): Promise<{ massage: string }> { - await this.examService.deleteExam(request, id); - return { massage: 'Exam deleted successfully' }; + ): Promise { + await this.examService.deleteExam(request.user.id, request.user.role, id); } } diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts index 77be4e0..d6ccb3c 100644 --- a/src/exam/exam.service.ts +++ b/src/exam/exam.service.ts @@ -3,6 +3,7 @@ import { Inject, NotFoundException, BadRequestException, + ForbiddenException, } from '@nestjs/common'; import { Repository, @@ -30,7 +31,8 @@ export class ExamService { ) {} async findAll( - request: AuthenticatedRequest, + userId: string, + role: Role, { page = 1, limit = 20, @@ -46,7 +48,11 @@ export class ExamService { limit, }); - const whereCondition = this.validateAndCreateCondition(request, search); + const whereCondition = this.validateAndCreateCondition( + userId, + role, + search, + ); const exam = await find({ where: whereCondition, relations: ['courseModule'], @@ -59,23 +65,24 @@ export class ExamService { } private validateAndCreateCondition( - request: AuthenticatedRequest, + userId: string, + role: Role, search: string, ): FindOptionsWhere | FindOptionsWhere[] { const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; - if (request.user.role === Role.STUDENT) { + if (role === Role.STUDENT) { return { ...baseSearch, status: ExamStatus.PUBLISHED }; } - if (request.user.role === Role.TEACHER) { + if (role === Role.TEACHER) { return [ { ...baseSearch, courseModule: { course: { teacher: { - id: request.user.id, + id: userId, }, }, }, @@ -87,7 +94,7 @@ export class ExamService { ]; } - if (request.user.role === Role.ADMIN) { + if (role === Role.ADMIN) { return { ...baseSearch }; } @@ -95,10 +102,11 @@ export class ExamService { } async findOne( - request: AuthenticatedRequest, + userId: string, + role: Role, options: FindOneOptions = {}, ): Promise { - const whereCondition = this.validateAndCreateCondition(request, ''); + const whereCondition = this.validateAndCreateCondition(userId, role, ''); const where = Array.isArray(whereCondition) ? [ @@ -136,22 +144,25 @@ export class ExamService { courseModule, }); - if (!exam) throw new NotFoundException("Can't create exam"); + if (!exam) throw new BadRequestException("Can't create exam"); await this.examRepository.save(exam); return exam; } async updateExam( - request: AuthenticatedRequest, + userId: string, + role: Role, id: string, updateExamDto: UpdateExamDto, ): Promise { - const examInData = await this.findOne(request, { where: { id } }); + const examInData = await this.findOne(userId, role, { where: { id } }); + if (this.checkPermission(userId, role, examInData) === false) + throw new ForbiddenException('Can not change this exam'); if ( examInData.status != ExamStatus.DRAFT && updateExamDto.status == ExamStatus.DRAFT ) { - throw new NotFoundException("Can't change status to draft"); + throw new ForbiddenException("Can't change status to draft"); } let courseModule = null; if (updateExamDto.courseModuleId) { @@ -168,7 +179,7 @@ export class ExamService { }; const exam = await this.examRepository.update(id, updateExam); - if (!exam) throw new NotFoundException("Can't update exam"); + if (!exam) throw new BadRequestException("Can't update exam"); return await this.examRepository.findOne({ where: { id }, relations: ['courseModule'], @@ -178,11 +189,12 @@ export class ExamService { }); } - async deleteExam(request: AuthenticatedRequest, id: string): Promise { + async deleteExam(userId: string, role: Role, id: string): Promise { try { - if (await this.findOne(request, { where: { id } })) { - await this.examRepository.delete(id); - } + const exam = await this.findOne(userId, role, { where: { id } }); + if (this.checkPermission(userId, role, exam) === false) + throw new ForbiddenException('Can not change this exam'); + await this.examRepository.delete(id); } catch (error) { if (error instanceof Error) throw new NotFoundException('Exam not found'); } @@ -191,4 +203,15 @@ export class ExamService { private selectPopulateCourseModule(): FindOptionsSelect { return { id: true, title: true, description: true, orderIndex: true }; } + + private checkPermission(userId: string, role: Role, exam: Exam): boolean { + switch (role) { + case Role.ADMIN: + return true; + case Role.TEACHER: + return exam.courseModule?.course?.teacher?.id === userId; + case Role.STUDENT: + return false; + } + } } diff --git a/src/question-option/question-option.controller.ts b/src/question-option/question-option.controller.ts index a00b0fd..c15cf5b 100644 --- a/src/question-option/question-option.controller.ts +++ b/src/question-option/question-option.controller.ts @@ -62,11 +62,15 @@ export class QuestionOptionController { @Req() request: AuthenticatedRequest, @Query() query: PaginateQueryDto, ): Promise { - return await this.questionOptionService.findAll(request, { - page: query.page, - limit: query.limit, - search: query.search, - }); + return await this.questionOptionService.findAll( + request.user.id, + request.user.role, + { + page: query.page, + limit: query.limit, + search: query.search, + }, + ); } @Get(':id') @@ -86,9 +90,13 @@ export class QuestionOptionController { ) id: string, ): Promise { - const questionOption = await this.questionOptionService.findOne(request, { - where: { id }, - }); + const questionOption = await this.questionOptionService.findOne( + request.user.id, + request.user.role, + { + where: { id }, + }, + ); return new QuestionOptionResponseDto(questionOption); } @@ -130,7 +138,8 @@ export class QuestionOptionController { questionId: string, ): Promise { return await this.questionOptionService.findQuestionOptionByQuestionId( - request, + request.user.id, + request.user.role, questionId, { page: query.page, @@ -179,7 +188,8 @@ export class QuestionOptionController { ): Promise { const questionOption = await this.questionOptionService.updateQuestionOption( - request, + request.user.id, + request.user.role, id, updateQuestionOptionDto, ); @@ -204,8 +214,11 @@ export class QuestionOptionController { }), ) id: string, - ): Promise<{ massage: string }> { - await this.questionOptionService.deleteQuestionOption(request, id); - return { massage: 'Question option deleted successfully' }; + ): Promise { + await this.questionOptionService.deleteQuestionOption( + request.user.id, + request.user.role, + id, + ); } } diff --git a/src/question-option/question-option.service.ts b/src/question-option/question-option.service.ts index a13f9be..88a30d3 100644 --- a/src/question-option/question-option.service.ts +++ b/src/question-option/question-option.service.ts @@ -3,6 +3,7 @@ import { Inject, NotFoundException, BadRequestException, + ForbiddenException, } from '@nestjs/common'; import { FindOneOptions, @@ -29,7 +30,8 @@ export class QuestionOptionService { ) {} async findAll( - request: AuthenticatedRequest, + userId: string, + role: Role, { page = 1, limit = 20, @@ -45,7 +47,11 @@ export class QuestionOptionService { limit, }); - const whereCondition = this.validateAndCreateCondition(request, search); + const whereCondition = this.validateAndCreateCondition( + userId, + role, + search, + ); const question = await find({ where: whereCondition, relations: ['question'], @@ -58,10 +64,11 @@ export class QuestionOptionService { } async findOne( - request: AuthenticatedRequest, + userId: string, + role: Role, options: FindOneOptions = {}, ): Promise { - const whereCondition = this.validateAndCreateCondition(request, ''); + const whereCondition = this.validateAndCreateCondition(userId, role, ''); const where = Array.isArray(whereCondition) ? [ @@ -87,7 +94,8 @@ export class QuestionOptionService { } async findQuestionOptionByQuestionId( - request: AuthenticatedRequest, + userId: string, + role: Role, questionId: string, { page = 1, @@ -104,7 +112,11 @@ export class QuestionOptionService { limit, }); - const whereCondition = this.validateAndCreateCondition(request, search); + const whereCondition = this.validateAndCreateCondition( + userId, + role, + search, + ); whereCondition['question'] = { id: questionId }; const question = await this.questionRepository.findOne({ @@ -126,12 +138,13 @@ export class QuestionOptionService { } private validateAndCreateCondition( - request: AuthenticatedRequest, + userId: string, + role: Role, search: string, ): FindOptionsWhere { const baseSearch = search ? { optionText: ILike(`%${search}%`) } : {}; - if (request.user.role === Role.STUDENT) { + if (role === Role.STUDENT) { return { ...baseSearch, question: { @@ -142,7 +155,7 @@ export class QuestionOptionService { }; } - if (request.user.role === Role.TEACHER) { + if (role === Role.TEACHER) { return { ...baseSearch, question: { @@ -153,7 +166,7 @@ export class QuestionOptionService { { courseModule: { course: { - teacher: { id: request.user.id }, + teacher: { id: userId }, }, }, }, @@ -162,7 +175,7 @@ export class QuestionOptionService { }; } - if (request.user.role === Role.ADMIN) { + if (role === Role.ADMIN) { return { ...baseSearch }; } @@ -198,11 +211,16 @@ export class QuestionOptionService { } async updateQuestionOption( - request: AuthenticatedRequest, + userId: string, + role: Role, id: string, updateQuestionOptionDto: UpdateQuestionOptionDto, ): Promise { - await this.findOne(request, { where: { id } }); + const questionOptionInData = await this.findOne(userId, role, { + where: { id }, + }); + if (this.checkPermission(userId, role, questionOptionInData) === false) + throw new ForbiddenException('Can not change this question option'); let question = null; if (updateQuestionOptionDto.questionId) { question = await this.questionRepository.findOne({ @@ -221,7 +239,7 @@ export class QuestionOptionService { updateQuestionOption, ); if (!questionOption) - throw new NotFoundException("Can't update question option"); + throw new BadRequestException("Can't update question option"); return await this.questionOptionRepository.findOne({ where: { id }, relations: ['question'], @@ -232,11 +250,16 @@ export class QuestionOptionService { } async deleteQuestionOption( - request: AuthenticatedRequest, + userId: string, + role: Role, id: string, ): Promise { try { - await this.findOne(request, { where: { id } }); + const questionOption = await this.findOne(userId, role, { + where: { id }, + }); + if (this.checkPermission(userId, role, questionOption) === false) + throw new ForbiddenException('Can not change this question option'); await this.questionOptionRepository.delete(id); } catch (error) { if (error instanceof Error) @@ -253,4 +276,22 @@ export class QuestionOptionService { orderIndex: true, }; } + + private checkPermission( + userId: string, + role: Role, + questionOption: QuestionOption, + ): boolean { + switch (role) { + case Role.ADMIN: + return true; + case Role.TEACHER: + return ( + questionOption.question?.exam?.courseModule?.course?.teacher?.id === + userId + ); + case Role.STUDENT: + return false; + } + } } diff --git a/src/question/question.controller.ts b/src/question/question.controller.ts index 21947a0..7e50204 100644 --- a/src/question/question.controller.ts +++ b/src/question/question.controller.ts @@ -62,11 +62,15 @@ export class QuestionController { @Req() request: AuthenticatedRequest, @Query() query: PaginateQueryDto, ): Promise { - return await this.questionService.findAll(request, { - page: query.page, - limit: query.limit, - search: query.search, - }); + return await this.questionService.findAll( + request.user.id, + request.user.role, + { + page: query.page, + limit: query.limit, + search: query.search, + }, + ); } @Get(':id') @@ -86,9 +90,13 @@ export class QuestionController { ) id: string, ): Promise { - const question = await this.questionService.findOne(request, { - where: { id }, - }); + const question = await this.questionService.findOne( + request.user.id, + request.user.role, + { + where: { id }, + }, + ); return new QuestionResponseDto(question); } @@ -129,11 +137,16 @@ export class QuestionController { ) examId: string, ): Promise { - return await this.questionService.findQuestionByExamId(request, examId, { - page: query.page, - limit: query.limit, - search: query.search, - }); + return await this.questionService.findQuestionByExamId( + request.user.id, + request.user.role, + examId, + { + page: query.page, + limit: query.limit, + search: query.search, + }, + ); } @Post() @@ -172,7 +185,8 @@ export class QuestionController { @Body() updateQuestionDto: UpdateQuestionDto, ): Promise { const question = await this.questionService.updateQuestion( - request, + request.user.id, + request.user.role, id, updateQuestionDto, ); @@ -197,8 +211,11 @@ export class QuestionController { }), ) id: string, - ): Promise<{ massage: string }> { - await this.questionService.deleteQuestion(request, id); - return { massage: 'Question deleted successfully' }; + ): Promise { + await this.questionService.deleteQuestion( + request.user.id, + request.user.role, + id, + ); } } diff --git a/src/question/question.service.ts b/src/question/question.service.ts index bf7d9e3..1f57b48 100644 --- a/src/question/question.service.ts +++ b/src/question/question.service.ts @@ -30,7 +30,8 @@ export class QuestionService { ) {} async findAll( - request: AuthenticatedRequest, + userId: string, + role: Role, { page = 1, limit = 20, @@ -46,7 +47,11 @@ export class QuestionService { limit, }); - const whereCondition = this.validateAndCreateCondition(request, search); + const whereCondition = this.validateAndCreateCondition( + userId, + role, + search, + ); const question = await find({ order: { orderIndex: 'ASC', @@ -62,10 +67,11 @@ export class QuestionService { } async findOne( - request: AuthenticatedRequest, + userId: string, + role: Role, options: FindOneOptions = {}, ): Promise { - const whereCondition = this.validateAndCreateCondition(request, ''); + const whereCondition = this.validateAndCreateCondition(userId, role, ''); const where = Array.isArray(whereCondition) ? [ @@ -91,7 +97,8 @@ export class QuestionService { } async findQuestionByExamId( - request: AuthenticatedRequest, + userId: string, + role: Role, examId: string, { page = 1, @@ -108,7 +115,11 @@ export class QuestionService { limit, }); - const whereCondition = this.validateAndCreateCondition(request, search); + const whereCondition = this.validateAndCreateCondition( + userId, + role, + search, + ); whereCondition['exam'] = { id: examId }; const exam = await this.examRepository.findOne({ @@ -151,12 +162,13 @@ export class QuestionService { } private validateAndCreateCondition( - request: AuthenticatedRequest, + userId: string, + role: Role, search: string, ): FindOptionsWhere { const baseSearch = search ? { question: ILike(`%${search}%`) } : {}; - if (request.user.role === Role.STUDENT) { + if (role === Role.STUDENT) { return { ...baseSearch, exam: { @@ -165,7 +177,7 @@ export class QuestionService { }; } - if (request.user.role === Role.TEACHER) { + if (role === Role.TEACHER) { return { ...baseSearch, exam: [ @@ -175,7 +187,7 @@ export class QuestionService { { courseModule: { course: { - teacher: { id: request.user.id }, + teacher: { id: userId }, }, }, }, @@ -183,7 +195,7 @@ export class QuestionService { }; } - if (request.user.role === Role.ADMIN) { + if (role === Role.ADMIN) { return { ...baseSearch }; } @@ -253,11 +265,14 @@ export class QuestionService { } async updateQuestion( - request: AuthenticatedRequest, + userId: string, + role: Role, id: string, updateQuestionDto: UpdateQuestionDto, ): Promise { - await this.findOne(request, { where: { id } }); + const question = await this.findOne(userId, role, { where: { id } }); + if (this.checkPermission(userId, role, question) === false) + throw new BadRequestException('Can not change this question'); let exam = null; if (updateQuestionDto.examId) { exam = await this.examRepository.findOne({ @@ -273,7 +288,7 @@ export class QuestionService { }; try { const question = await this.questionRepository.update(id, updateQuestion); - if (!question) throw new NotFoundException("Can't update question"); + if (!question) throw new BadRequestException("Can't update question"); return await this.questionRepository.findOne({ where: { id }, relations: ['exam'], @@ -293,12 +308,11 @@ export class QuestionService { } } - async deleteQuestion( - request: AuthenticatedRequest, - id: string, - ): Promise { + async deleteQuestion(userId: string, role: Role, id: string): Promise { try { - const question = await this.findOne(request, { where: { id } }); + const question = await this.findOne(userId, role, { where: { id } }); + if (this.checkPermission(userId, role, question) === false) + throw new BadRequestException('Can not change this question'); await this.questionRepository.delete(id); await this.reOrderIndex(question.examId); } catch (error) { @@ -319,4 +333,19 @@ export class QuestionService { status: true, }; } + + private checkPermission( + userId: string, + role: Role, + question: Question, + ): boolean { + switch (role) { + case Role.ADMIN: + return true; + case Role.TEACHER: + return question.exam.courseModule?.course?.teacher?.id == userId; + case Role.STUDENT: + return false; + } + } } From 5f00cb5bb34e4f4ebd5e47716a0ace22d16fd24a Mon Sep 17 00:00:00 2001 From: ganthepro Date: Mon, 18 Nov 2024 13:47:20 +0700 Subject: [PATCH 077/155] style: format code in chapter.entity.ts for consistency and readability --- src/chapter/chapter.entity.ts | 36 +++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/chapter/chapter.entity.ts b/src/chapter/chapter.entity.ts index 6a414ea..214c033 100644 --- a/src/chapter/chapter.entity.ts +++ b/src/chapter/chapter.entity.ts @@ -1,14 +1,14 @@ import { CourseModule } from 'src/course-module/course-module.entity'; import { Progress } from 'src/progress/progress.entity'; import { - Column, - CreateDateColumn, - Entity, - JoinColumn, - ManyToOne, - OneToMany, - PrimaryGeneratedColumn, - UpdateDateColumn, + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + OneToMany, + PrimaryGeneratedColumn, + UpdateDateColumn, } from 'typeorm'; @Entity() @@ -28,17 +28,17 @@ export class Chapter { }) description: string; - @ManyToOne(() => CourseModule, (module) => module.chapters, { - onDelete: 'CASCADE', - nullable: false, - }) - @JoinColumn({ name: 'module_id' }) - module: CourseModule; - @Column({ name: 'module_id' }) - moduleId: string; + @ManyToOne(() => CourseModule, (module) => module.chapters, { + onDelete: 'CASCADE', + nullable: false, + }) + @JoinColumn({ name: 'module_id' }) + module: CourseModule; + @Column({ name: 'module_id' }) + moduleId: string; - @OneToMany(() => Progress, (progress) => progress.chapter) - progresses: Progress[]; + @OneToMany(() => Progress, (progress) => progress.chapter) + progresses: Progress[]; @Column({ type: String, From 04aded21aa0f805bc4796689c1d83192284dc641 Mon Sep 17 00:00:00 2001 From: Potsawee Date: Mon, 18 Nov 2024 14:41:43 +0700 Subject: [PATCH 078/155] add swagger response to category and reward --- src/category/category.controller.ts | 16 ++++++--- src/category/dtos/category-response.dto.ts | 39 +++++++++++++++++++++- src/reward/reward.controllers.ts | 4 +-- 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/category/category.controller.ts b/src/category/category.controller.ts index 6689d69..175c85e 100644 --- a/src/category/category.controller.ts +++ b/src/category/category.controller.ts @@ -7,25 +7,30 @@ import { ParseUUIDPipe, Patch, Post, + Injectable, + Get, } from '@nestjs/common'; import { CategoryService } from './category.service'; import { categoryResponseDto } from './dtos/category-response.dto'; -import { Get } from '@nestjs/common'; import { CreateCategoryDto } from './dtos/create-cateory.dto'; import { updateCategoryDto } from './dtos/update-category.dto'; import { Public } from 'src/shared/decorators/public.decorator'; import { ApiBearerAuth, ApiResponse, ApiTags } from '@nestjs/swagger'; import { Category } from './category.entity'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums'; @Controller('category') +@Injectable() @ApiTags('Category') export class CategoryController { constructor(private readonly categoryService: CategoryService) {} @Post() + @Roles(Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, - type: Category, + type: categoryResponseDto, description: 'create category', }) @ApiBearerAuth() @@ -39,7 +44,7 @@ export class CategoryController { @Public() @ApiResponse({ status: HttpStatus.OK, - type: Category, + type: categoryResponseDto, isArray: true, description: 'get all categories', }) @@ -52,7 +57,7 @@ export class CategoryController { @Public() @ApiResponse({ status: HttpStatus.OK, - type: Category, + type: categoryResponseDto, description: 'get one category', }) async findOne( @@ -70,10 +75,12 @@ export class CategoryController { } @Patch(':id') + @Roles(Role.ADMIN) @ApiBearerAuth() @ApiResponse({ status: HttpStatus.OK, description: 'edit category', + type: categoryResponseDto, }) async update( @Param( @@ -90,6 +97,7 @@ export class CategoryController { } @Delete(':id') + @Roles(Role.ADMIN) @ApiBearerAuth() @ApiResponse({ status: HttpStatus.OK, diff --git a/src/category/dtos/category-response.dto.ts b/src/category/dtos/category-response.dto.ts index cb4d5ef..7886d24 100644 --- a/src/category/dtos/category-response.dto.ts +++ b/src/category/dtos/category-response.dto.ts @@ -1,12 +1,49 @@ import { Slug } from 'src/category/enums/slug.enum'; import { Category } from '../category.entity'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; export class categoryResponseDto { + @ApiProperty({ + description: 'category id', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) id: string; + + @ApiProperty({ + description: 'name of category', + type: String, + example: 'javascript', + }) title: string; - description: string; + + @ApiPropertyOptional({ + description: 'description of category (optional)', + type: String, + example: 'high level programming language', + }) + description?: string; + + @ApiProperty({ + description: 'slug', + type: String, + example: Slug.COURSE, + enum: Slug, + }) slug: Slug; + + @ApiProperty({ + description: 'category created date', + type: Date, + example: new Date(), + }) createdAt: Date; + + @ApiProperty({ + description: 'category updated at', + type: Date, + example: new Date(), + }) updatedAt: Date; constructor(category: Category) { diff --git a/src/reward/reward.controllers.ts b/src/reward/reward.controllers.ts index b97cecc..a16ad4e 100644 --- a/src/reward/reward.controllers.ts +++ b/src/reward/reward.controllers.ts @@ -31,7 +31,7 @@ export class RewardController { @Public() @ApiResponse({ status: HttpStatus.OK, - type: Reward, + type: RewardResponseDto, description: 'get all reward', isArray: true, }) @@ -44,7 +44,7 @@ export class RewardController { @Public() @ApiResponse({ status: HttpStatus.OK, - type: Reward, + type: RewardResponseDto, description: 'get reward', }) async findOne( From 3e19eafdf6786866fe05f635199f3b2026aa30a0 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Mon, 18 Nov 2024 15:00:26 +0700 Subject: [PATCH 079/155] feat: update chat room DTO and entity for improved structure and naming consistency --- src/chapter/chapter.entity.ts | 4 + src/chapter/chapter.service.ts | 257 ++++++++++----------- src/chat-room/chat-room.entity.ts | 5 +- src/chat-room/dtos/create-chat-room.dto.ts | 4 +- src/shared/configs/database.config.ts | 10 +- 5 files changed, 140 insertions(+), 140 deletions(-) diff --git a/src/chapter/chapter.entity.ts b/src/chapter/chapter.entity.ts index 214c033..8d466c0 100644 --- a/src/chapter/chapter.entity.ts +++ b/src/chapter/chapter.entity.ts @@ -1,3 +1,4 @@ +import { ChatRoom } from 'src/chat-room/chat-room.entity'; import { CourseModule } from 'src/course-module/course-module.entity'; import { Progress } from 'src/progress/progress.entity'; import { @@ -37,6 +38,9 @@ export class Chapter { @Column({ name: 'module_id' }) moduleId: string; + @OneToMany(() => ChatRoom, (chatRoom) => chatRoom.chapter) + chatRooms: ChatRoom[]; + @OneToMany(() => Progress, (progress) => progress.chapter) progresses: Progress[]; diff --git a/src/chapter/chapter.service.ts b/src/chapter/chapter.service.ts index 59b483b..94a0427 100644 --- a/src/chapter/chapter.service.ts +++ b/src/chapter/chapter.service.ts @@ -26,22 +26,22 @@ export class ChapterService { page = 1, limit = 20, search = '', - userId, - role, + userId, + role, }: { page?: number; limit?: number; search?: string; - userId: string; - role: Role; + userId: string; + role: Role; }): Promise { const { find } = await createPagination(this.chapterRepository, { page, limit, }); - const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; - const whereCondition = this.buildWhereCondition(userId, role, baseSearch); + const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; + const whereCondition = this.buildWhereCondition(userId, role, baseSearch); const chapters = await find({ where: whereCondition, @@ -53,13 +53,13 @@ export class ChapterService { return chapters; } - async findOne( - userId: string, - role: Role, - options: FindOneOptions, - ): Promise { - const baseWhere = options.where as FindOptionsWhere; - const whereCondition = this.buildWhereCondition(userId, role, baseWhere); + async findOne( + userId: string, + role: Role, + options: FindOneOptions, + ): Promise { + const baseWhere = options.where as FindOptionsWhere; + const whereCondition = this.buildWhereCondition(userId, role, baseWhere); const chapter = await this.chapterRepository.findOne({ where: whereCondition, @@ -92,29 +92,28 @@ export class ChapterService { return nextOrderIndex.length ? nextOrderIndex[0] + 1 : 1; } - async create(createChapterDto: CreateChapterDto): Promise { - - let orderIndex = await this.validateAndGetNextOrderIndex( - createChapterDto.moduleId, - ); + async create(createChapterDto: CreateChapterDto): Promise { + let orderIndex = await this.validateAndGetNextOrderIndex( + createChapterDto.moduleId, + ); - const chapter = this.chapterRepository.create({...createChapterDto, orderIndex: orderIndex}); - await this.chapterRepository.save(chapter); + const createdChapter = this.chapterRepository.create({ ...createChapterDto, orderIndex: orderIndex }); + const savedChapter = await this.chapterRepository.save(createdChapter); await this.chatRoomService.create({ - name: `${chapter.title} Questions`, + title: `${savedChapter.title} Questions`, type: ChatRoomType.QUESTION, - chapterId: chapter.id, + chapterId: savedChapter.id, status: ChatRoomStatus.ACTIVE, }); await this.chatRoomService.create({ - name: `${chapter.title} Discussion`, + title: `${savedChapter.title} Discussion`, type: ChatRoomType.DISCUSSION, - chapterId: chapter.id, + chapterId: savedChapter.id, status: ChatRoomStatus.ACTIVE, }); - return chapter; + return savedChapter; } async reorderModules(moduleId: string): Promise { @@ -130,41 +129,42 @@ export class ChapterService { await this.chapterRepository.save(modulesToReorder); } - async update( - id: string, - updateChapterDto: UpdateChapterDto, - ): Promise { - const chapter = await this.chapterRepository.findOne({ where: { id } }); + async update( + id: string, + updateChapterDto: UpdateChapterDto, + ): Promise { + const chapter = await this.chapterRepository.findOne({ where: { id } }); - if (!chapter) { - throw new NotFoundException('Chapter not found'); - } - if (updateChapterDto.orderIndex != null) { - await this.validateOrderIndex(chapter.moduleId, updateChapterDto.orderIndex); - } - if ( - updateChapterDto.orderIndex && - updateChapterDto.orderIndex !== chapter.orderIndex - ) { - const existingChapter = await this.chapterRepository.findOne({ - where: { - moduleId: chapter.moduleId, - orderIndex: updateChapterDto.orderIndex }, - }); - - if (existingChapter) { - await this.chapterRepository.update(existingChapter.id, { orderIndex: chapter.orderIndex }); - } - } + if (!chapter) { + throw new NotFoundException('Chapter not found'); + } + if (updateChapterDto.orderIndex != null) { + await this.validateOrderIndex(chapter.moduleId, updateChapterDto.orderIndex); + } + if ( + updateChapterDto.orderIndex && + updateChapterDto.orderIndex !== chapter.orderIndex + ) { + const existingChapter = await this.chapterRepository.findOne({ + where: { + moduleId: chapter.moduleId, + orderIndex: updateChapterDto.orderIndex + }, + }); + + if (existingChapter) { + await this.chapterRepository.update(existingChapter.id, { orderIndex: chapter.orderIndex }); + } + } - Object.assign(chapter, updateChapterDto); - await this.chapterRepository.save(chapter); + Object.assign(chapter, updateChapterDto); + await this.chapterRepository.save(chapter); return chapter; } - async remove(id: string): Promise { - const chapter = await this.chapterRepository.findOne({ where: { id } }); + async remove(id: string): Promise { + const chapter = await this.chapterRepository.findOne({ where: { id } }); if (!chapter) { throw new BadRequestException('Chapter not found'); @@ -174,85 +174,84 @@ export class ChapterService { await this.reorderModules(chapter.moduleId); - return result; - } - - private async validateOrderIndex(moduleId: string, orderIndex: number): Promise { - const existingModules = await this.chapterRepository.find({ - where: { moduleId }, - order: { orderIndex: 'ASC' }, - }); - if (existingModules.length === 0) { - if (orderIndex !== 1) { - throw new BadRequestException( - 'Order index should be 1 when there are no modules in the course' - ); - } - return; + return result; } - const minIndex = 1; - const maxIndex = existingModules[existingModules.length - 1].orderIndex; - if (orderIndex < minIndex || orderIndex > maxIndex) { - throw new BadRequestException( - `Order index must be between ${minIndex} and ${maxIndex}` - ); - } - } - async validateOwnership(id: string, userId: string): Promise { - const chapter = await this.chapterRepository.findOne({ where: { id }, relations: { module: { course: { teacher: true } } } }); - if(!chapter) throw new NotFoundException('Chapter not found'); - if (chapter.module.course.teacher.id !== userId) - throw new BadRequestException('You can only access your own courses'); - } - private buildWhereCondition( - userId: string, - role: Role, - baseCondition: FindOptionsWhere = {} - ) - { - const conditions: Record< - Role, - () => FindOptionsWhere | FindOptionsWhere[] - > = { - [Role.STUDENT]: () => ({ - ...baseCondition, - module: { - course: { - status: CourseStatus.PUBLISHED, - }, - }, - }), - [Role.TEACHER]: () => [ - { - ...baseCondition, - module: { - course: { - status: CourseStatus.PUBLISHED, - }, - }, - }, - { - ...baseCondition, - module: { - course: { - teacher: { - id: userId, - }, - }, - }, - }, - ], - [Role.ADMIN]: () => baseCondition, - }; - - const buildCondition = conditions[role]; + private async validateOrderIndex(moduleId: string, orderIndex: number): Promise { + const existingModules = await this.chapterRepository.find({ + where: { moduleId }, + order: { orderIndex: 'ASC' }, + }); + if (existingModules.length === 0) { + if (orderIndex !== 1) { + throw new BadRequestException( + 'Order index should be 1 when there are no modules in the course' + ); + } + return; + } + const minIndex = 1; + const maxIndex = existingModules[existingModules.length - 1].orderIndex; - if (!buildCondition) { - throw new BadRequestException('Invalid role'); + if (orderIndex < minIndex || orderIndex > maxIndex) { + throw new BadRequestException( + `Order index must be between ${minIndex} and ${maxIndex}` + ); + } + } + async validateOwnership(id: string, userId: string): Promise { + const chapter = await this.chapterRepository.findOne({ where: { id }, relations: { module: { course: { teacher: true } } } }); + if (!chapter) throw new NotFoundException('Chapter not found'); + if (chapter.module.course.teacher.id !== userId) + throw new BadRequestException('You can only access your own courses'); } + private buildWhereCondition( + userId: string, + role: Role, + baseCondition: FindOptionsWhere = {} + ) { + const conditions: Record< + Role, + () => FindOptionsWhere | FindOptionsWhere[] + > = { + [Role.STUDENT]: () => ({ + ...baseCondition, + module: { + course: { + status: CourseStatus.PUBLISHED, + }, + }, + }), + [Role.TEACHER]: () => [ + { + ...baseCondition, + module: { + course: { + status: CourseStatus.PUBLISHED, + }, + }, + }, + { + ...baseCondition, + module: { + course: { + teacher: { + id: userId, + }, + }, + }, + }, + ], + [Role.ADMIN]: () => baseCondition, + }; + + const buildCondition = conditions[role]; + + if (!buildCondition) { + throw new BadRequestException('Invalid role'); + } - return buildCondition(); - } + return buildCondition(); + } } diff --git a/src/chat-room/chat-room.entity.ts b/src/chat-room/chat-room.entity.ts index cc50cf8..80b6f29 100644 --- a/src/chat-room/chat-room.entity.ts +++ b/src/chat-room/chat-room.entity.ts @@ -1,4 +1,4 @@ -import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToOne, JoinColumn } from "typeorm"; +import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToOne, JoinColumn, ManyToOne } from "typeorm"; import { Chapter } from "src/chapter/chapter.entity"; import { ChatRoomType, ChatRoomStatus } from "./enums"; @@ -7,7 +7,7 @@ export class ChatRoom { @PrimaryGeneratedColumn("uuid") id: string; - @OneToOne(() => Chapter, { + @ManyToOne(() => Chapter, (chapter) => chapter.chatRooms, { onDelete: "CASCADE", nullable: false, eager: true, @@ -42,7 +42,6 @@ export class ChatRoom { createdAt: Date; @Column({ - nullable: false, default: 0, type: "int", }) diff --git a/src/chat-room/dtos/create-chat-room.dto.ts b/src/chat-room/dtos/create-chat-room.dto.ts index 3ddbe06..c8eb48c 100644 --- a/src/chat-room/dtos/create-chat-room.dto.ts +++ b/src/chat-room/dtos/create-chat-room.dto.ts @@ -6,11 +6,11 @@ export class CreateChatRoomDto { @IsString() @IsNotEmpty() @ApiProperty({ - description: 'ChatRoom name', + description: 'ChatRoom title', type: String, example: 'Chat Room 1', }) - name: string; + title: string; @IsEnum(ChatRoomType) @ApiProperty({ diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index c0ef04e..7d3ae68 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -20,6 +20,8 @@ import { GLOBAL_CONFIG } from '../constants/global-config.constant'; import { ExamAnswer } from 'src/exam-answer/exam-answer.entity'; import { Reward } from 'src/reward/reward.entity'; import { UserReward } from 'src/userReward/user-reward.entity'; +import { ChatRoom } from 'src/chat-room/chat-room.entity'; +import { ChatMessage } from 'src/chat-message/chat-message.entity'; const configService = new ConfigService(); @@ -33,19 +35,13 @@ export const databaseConfig: DataSourceOptions = { logging: configService.get(GLOBAL_CONFIG.IS_DEVELOPMENT), entities: [ User, - UserStreak, - Category, - Course, - CourseModule, - Chapter, Reward, UserReward, - , Enrollment, Exam, Progress, @@ -56,6 +52,8 @@ export const databaseConfig: DataSourceOptions = { UserOccupation, UserBackgroundTopic, UserBackground, + ChatRoom, + ChatMessage, ], }; From 3db4e915e0bba880a3afdf932813fbe8145c6a5b Mon Sep 17 00:00:00 2001 From: ganthepro Date: Mon, 18 Nov 2024 15:48:19 +0700 Subject: [PATCH 080/155] style: standardize enum formatting and improve code consistency across various files --- .../authenticated-request.interface.ts | 2 +- src/category/category.module.ts | 13 +- src/chapter/chapter.controller.ts | 23 +- src/chapter/chapter.entity.ts | 146 +++--- src/chapter/chapter.module.ts | 22 +- src/chapter/chapter.service.ts | 447 +++++++++--------- src/chapter/dtos/chapter-response.dto.ts | 178 +++---- src/chapter/dtos/create-chapter.dto.ts | 9 +- src/chapter/dtos/update-chapter.dto.ts | 17 +- src/chat-message/chat-message.controller.ts | 207 ++++---- src/chat-message/chat-message.entity.ts | 92 ++-- src/chat-message/chat-message.module.ts | 28 +- src/chat-message/chat-message.providers.ts | 11 +- src/chat-message/chat-message.service.ts | 151 +++--- .../dtos/create-chat-message.dto.ts | 78 +-- src/chat-message/dtos/index.ts | 2 +- .../paginated-chat-message-response.dto.ts | 115 ++--- .../dtos/update-chat-message.dto.ts | 44 +- .../enums/chat-message-type.enum.ts | 8 +- src/chat-room/chat-room.controller.ts | 281 ++++++----- src/chat-room/chat-room.entity.ts | 111 +++-- src/chat-room/chat-room.module.ts | 37 +- src/chat-room/chat-room.service.ts | 132 +++--- src/chat-room/dtos/create-chat-room.dto.ts | 72 +-- src/chat-room/dtos/index.ts | 2 +- .../dtos/paginated-chat-room-response.dto.ts | 100 ++-- src/chat-room/dtos/update-chat-room.dto.ts | 56 +-- src/chat-room/enums/chat-room-status.enum.ts | 8 +- src/chat-room/enums/chat-room-type.enum.ts | 6 +- src/chat-room/enums/index.ts | 2 +- .../guards/chat-room-ownership.guard.ts | 48 +- src/course-module/course-module.controller.ts | 23 +- src/course-module/course-module.module.ts | 18 +- src/course-module/course-module.service.ts | 45 +- .../dtos/create-course-module.dto.ts | 1 - .../dtos/update-course-module.dto.ts | 17 +- src/course/course.controller.ts | 15 +- src/course/course.module.ts | 8 +- src/course/course.service.ts | 14 +- src/enrollment/enrollment.controller.ts | 203 ++++---- src/enrollment/enrollment.service.ts | 123 +++-- src/exam/dtos/create-exam.dto.ts | 132 +++--- src/exam/dtos/update-exam.dto.ts | 2 +- src/exam/exam.providers.ts | 10 +- src/exam/exam.service.ts | 9 +- src/file/enums/folder.enum.ts | 10 +- src/file/file.module.ts | 10 +- src/file/file.service.ts | 130 ++--- src/question/dtos/update-question.dto.ts | 2 +- src/question/question.module.ts | 32 +- src/question/question.providers.ts | 10 +- .../constants/global-config.constant.ts | 8 +- .../decorators/course-ownership.decorator.ts | 17 +- src/shared/enums/exam-status.enum.ts | 8 +- src/shared/guards/course-ownership.guard.ts | 72 +-- src/shared/pagination/typeorm.ts | 94 ++-- .../user-background-topic.controller.ts | 38 +- .../user-background.service.ts | 10 +- src/user-streak/user-streak.module.ts | 5 +- src/user/user.controller.ts | 446 ++++++++--------- src/user/user.module.ts | 13 +- src/user/user.service.ts | 103 ++-- 62 files changed, 2119 insertions(+), 1957 deletions(-) diff --git a/src/auth/interfaces/authenticated-request.interface.ts b/src/auth/interfaces/authenticated-request.interface.ts index 8cc70df..3177ac6 100644 --- a/src/auth/interfaces/authenticated-request.interface.ts +++ b/src/auth/interfaces/authenticated-request.interface.ts @@ -2,5 +2,5 @@ import { JwtPayloadDto } from '../dtos/jwt-payload.dto'; import { Request } from 'express'; export interface AuthenticatedRequest extends Request { - user: JwtPayloadDto; + user: JwtPayloadDto; } diff --git a/src/category/category.module.ts b/src/category/category.module.ts index 9f041c1..da00eab 100644 --- a/src/category/category.module.ts +++ b/src/category/category.module.ts @@ -7,12 +7,9 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { Category } from './category.entity'; @Module({ - imports: [ - DatabaseModule, - TypeOrmModule.forFeature([Category]), - ], - controllers: [CategoryController], - providers: [...categoryProviders, CategoryService], - exports: [CategoryService], + imports: [DatabaseModule, TypeOrmModule.forFeature([Category])], + controllers: [CategoryController], + providers: [...categoryProviders, CategoryService], + exports: [CategoryService], }) -export class CategoryModule { } +export class CategoryModule {} diff --git a/src/chapter/chapter.controller.ts b/src/chapter/chapter.controller.ts index 69d4be1..611823b 100644 --- a/src/chapter/chapter.controller.ts +++ b/src/chapter/chapter.controller.ts @@ -41,7 +41,10 @@ import { Course } from 'src/course/course.entity'; @ApiBearerAuth() @Injectable() export class ChapterController { - constructor(private readonly chapterService: ChapterService, private readonly courseModuleService: CourseModuleService) { } + constructor( + private readonly chapterService: ChapterService, + private readonly courseModuleService: CourseModuleService, + ) {} @Get() @ApiResponse({ @@ -90,7 +93,9 @@ export class ChapterController { @Req() request: AuthenticatedRequest, @Param('id', ParseUUIDPipe) id: string, ): Promise { - return this.chapterService.findOne(request.user.id, request.user.role, { where: { id } }); + return this.chapterService.findOne(request.user.id, request.user.role, { + where: { id }, + }); } @Post() @@ -105,13 +110,16 @@ export class ChapterController { @Body() createChapterDto: CreateChapterDto, ): Promise { if (createChapterDto.moduleId != null) { - await this.courseModuleService.validateOwnership(createChapterDto.moduleId, request.user.id); + await this.courseModuleService.validateOwnership( + createChapterDto.moduleId, + request.user.id, + ); } return this.chapterService.create(createChapterDto); } @Patch(':id') - @CourseOwnership({adminDraftOnly: true}) + @CourseOwnership({ adminDraftOnly: true }) @ApiResponse({ status: HttpStatus.OK, type: ChapterResponseDto, @@ -128,7 +136,10 @@ export class ChapterController { @Body() updateChapterDto: UpdateChapterDto, ): Promise { if (updateChapterDto.moduleId != null) { - await this.courseModuleService.validateOwnership(updateChapterDto.moduleId, request.user.id); + await this.courseModuleService.validateOwnership( + updateChapterDto.moduleId, + request.user.id, + ); } return this.chapterService.update(id, updateChapterDto); } @@ -151,5 +162,3 @@ export class ChapterController { return this.chapterService.remove(id); } } - - diff --git a/src/chapter/chapter.entity.ts b/src/chapter/chapter.entity.ts index 8d466c0..97ed6e4 100644 --- a/src/chapter/chapter.entity.ts +++ b/src/chapter/chapter.entity.ts @@ -2,94 +2,94 @@ import { ChatRoom } from 'src/chat-room/chat-room.entity'; import { CourseModule } from 'src/course-module/course-module.entity'; import { Progress } from 'src/progress/progress.entity'; import { - Column, - CreateDateColumn, - Entity, - JoinColumn, - ManyToOne, - OneToMany, - PrimaryGeneratedColumn, - UpdateDateColumn, + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + OneToMany, + PrimaryGeneratedColumn, + UpdateDateColumn, } from 'typeorm'; @Entity() export class Chapter { - @PrimaryGeneratedColumn('uuid') - id: string; + @PrimaryGeneratedColumn('uuid') + id: string; - @Column({ - type: String, - nullable: false, - }) - title: string; + @Column({ + type: String, + nullable: false, + }) + title: string; - @Column({ - type: String, - nullable: false, - }) - description: string; + @Column({ + type: String, + nullable: false, + }) + description: string; - @ManyToOne(() => CourseModule, (module) => module.chapters, { - onDelete: 'CASCADE', - nullable: false, - }) - @JoinColumn({ name: 'module_id' }) - module: CourseModule; - @Column({ name: 'module_id' }) - moduleId: string; + @ManyToOne(() => CourseModule, (module) => module.chapters, { + onDelete: 'CASCADE', + nullable: false, + }) + @JoinColumn({ name: 'module_id' }) + module: CourseModule; + @Column({ name: 'module_id' }) + moduleId: string; - @OneToMany(() => ChatRoom, (chatRoom) => chatRoom.chapter) - chatRooms: ChatRoom[]; + @OneToMany(() => ChatRoom, (chatRoom) => chatRoom.chapter) + chatRooms: ChatRoom[]; - @OneToMany(() => Progress, (progress) => progress.chapter) - progresses: Progress[]; + @OneToMany(() => Progress, (progress) => progress.chapter) + progresses: Progress[]; - @Column({ - type: String, - nullable: false, - }) - videoUrl: string; + @Column({ + type: String, + nullable: false, + }) + videoUrl: string; - @Column({ - type: String, - nullable: false, - }) - content: string; + @Column({ + type: String, + nullable: false, + }) + content: string; - @Column({ - type: String, - nullable: false, - }) - summary: string; + @Column({ + type: String, + nullable: false, + }) + summary: string; - @Column({ - type: Number, - nullable: false, - }) - duration: number; + @Column({ + type: Number, + nullable: false, + }) + duration: number; - @Column({ - type: Number, - nullable: false, - }) - orderIndex: number; + @Column({ + type: Number, + nullable: false, + }) + orderIndex: number; - @Column({ - type: Boolean, - nullable: false, - default: true, - }) - isPreview: boolean; + @Column({ + type: Boolean, + nullable: false, + default: true, + }) + isPreview: boolean; - @CreateDateColumn({ - type: 'timestamp', - name: 'created_at', - }) - createdAt: Date; + @CreateDateColumn({ + type: 'timestamp', + name: 'created_at', + }) + createdAt: Date; - @UpdateDateColumn({ - type: 'timestamp', - name: 'updated_at', - }) - updatedAt: Date; + @UpdateDateColumn({ + type: 'timestamp', + name: 'updated_at', + }) + updatedAt: Date; } diff --git a/src/chapter/chapter.module.ts b/src/chapter/chapter.module.ts index 4d2a842..f53abbb 100644 --- a/src/chapter/chapter.module.ts +++ b/src/chapter/chapter.module.ts @@ -4,18 +4,20 @@ import { ChapterController } from './chapter.controller'; import { Chapter } from './chapter.entity'; import { chapterProviders } from './chapter.provider'; import { ChapterService } from './chapter.service'; -import { CourseModuleModule } from 'src/course-module/course-module.module';import { TypeOrmModule } from '@nestjs/typeorm'; +import { CourseModuleModule } from 'src/course-module/course-module.module'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { CourseModule } from 'src/course-module/course-module.entity'; import { ChatRoomModule } from 'src/chat-room/chat-room.module'; @Module({ - imports: [ - DatabaseModule, CourseModuleModule, - TypeOrmModule.forFeature([Chapter, CourseModule]), - ChatRoomModule, - ], - controllers: [ChapterController], - providers: [...chapterProviders, ChapterService], - exports: [ChapterService], + imports: [ + DatabaseModule, + CourseModuleModule, + TypeOrmModule.forFeature([Chapter, CourseModule]), + ChatRoomModule, + ], + controllers: [ChapterController], + providers: [...chapterProviders, ChapterService], + exports: [ChapterService], }) -export class ChapterModule { } +export class ChapterModule {} diff --git a/src/chapter/chapter.service.ts b/src/chapter/chapter.service.ts index 94a0427..30e494e 100644 --- a/src/chapter/chapter.service.ts +++ b/src/chapter/chapter.service.ts @@ -1,7 +1,7 @@ import { - BadRequestException, - Injectable, - NotFoundException, + BadRequestException, + Injectable, + NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { createPagination } from 'src/shared/pagination'; @@ -16,242 +16,253 @@ import { ChatRoomStatus, ChatRoomType } from 'src/chat-room/enums'; @Injectable() export class ChapterService { - constructor( - @InjectRepository(Chapter) - private readonly chapterRepository: Repository, - private readonly chatRoomService: ChatRoomService, - ) { } - - async findAll({ - page = 1, - limit = 20, - search = '', - userId, - role, - }: { - page?: number; - limit?: number; - search?: string; - userId: string; - role: Role; - }): Promise { - const { find } = await createPagination(this.chapterRepository, { - page, - limit, - }); - - const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; - const whereCondition = this.buildWhereCondition(userId, role, baseSearch); - - const chapters = await find({ - where: whereCondition, - relations: { - module: true, - }, - }).run(); - - return chapters; - } - - async findOne( - userId: string, - role: Role, - options: FindOneOptions, - ): Promise { - const baseWhere = options.where as FindOptionsWhere; - const whereCondition = this.buildWhereCondition(userId, role, baseWhere); - - const chapter = await this.chapterRepository.findOne({ - where: whereCondition, - relations: { - module: true, - }, - }); - - if (!chapter) { - throw new NotFoundException('Chapter not found'); - } - - return chapter; + constructor( + @InjectRepository(Chapter) + private readonly chapterRepository: Repository, + private readonly chatRoomService: ChatRoomService, + ) {} + + async findAll({ + page = 1, + limit = 20, + search = '', + userId, + role, + }: { + page?: number; + limit?: number; + search?: string; + userId: string; + role: Role; + }): Promise { + const { find } = await createPagination(this.chapterRepository, { + page, + limit, + }); + + const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; + const whereCondition = this.buildWhereCondition(userId, role, baseSearch); + + const chapters = await find({ + where: whereCondition, + relations: { + module: true, + }, + }).run(); + + return chapters; + } + + async findOne( + userId: string, + role: Role, + options: FindOneOptions, + ): Promise { + const baseWhere = options.where as FindOptionsWhere; + const whereCondition = this.buildWhereCondition(userId, role, baseWhere); + + const chapter = await this.chapterRepository.findOne({ + where: whereCondition, + relations: { + module: true, + }, + }); + + if (!chapter) { + throw new NotFoundException('Chapter not found'); } - async validateAndGetNextOrderIndex(moduleId: string): Promise { - const existingChapter = await this.chapterRepository.find({ - where: { moduleId }, - order: { orderIndex: 'DESC' }, - }); + return chapter; + } - const nextOrderIndex = existingChapter.map((chapter) => chapter.orderIndex); - const hasDuplicates = - new Set(nextOrderIndex).size !== nextOrderIndex.length; + async validateAndGetNextOrderIndex(moduleId: string): Promise { + const existingChapter = await this.chapterRepository.find({ + where: { moduleId }, + order: { orderIndex: 'DESC' }, + }); - if (hasDuplicates) { - throw new BadRequestException('Order index is duplicated'); - } + const nextOrderIndex = existingChapter.map((chapter) => chapter.orderIndex); + const hasDuplicates = + new Set(nextOrderIndex).size !== nextOrderIndex.length; - return nextOrderIndex.length ? nextOrderIndex[0] + 1 : 1; + if (hasDuplicates) { + throw new BadRequestException('Order index is duplicated'); } - async create(createChapterDto: CreateChapterDto): Promise { - - let orderIndex = await this.validateAndGetNextOrderIndex( - createChapterDto.moduleId, - ); - - - const createdChapter = this.chapterRepository.create({ ...createChapterDto, orderIndex: orderIndex }); - const savedChapter = await this.chapterRepository.save(createdChapter); - await this.chatRoomService.create({ - title: `${savedChapter.title} Questions`, - type: ChatRoomType.QUESTION, - chapterId: savedChapter.id, - status: ChatRoomStatus.ACTIVE, - }); - await this.chatRoomService.create({ - title: `${savedChapter.title} Discussion`, - type: ChatRoomType.DISCUSSION, - chapterId: savedChapter.id, - status: ChatRoomStatus.ACTIVE, - }); - return savedChapter; + return nextOrderIndex.length ? nextOrderIndex[0] + 1 : 1; + } + + async create(createChapterDto: CreateChapterDto): Promise { + let orderIndex = await this.validateAndGetNextOrderIndex( + createChapterDto.moduleId, + ); + + const createdChapter = this.chapterRepository.create({ + ...createChapterDto, + orderIndex: orderIndex, + }); + const savedChapter = await this.chapterRepository.save(createdChapter); + await this.chatRoomService.create({ + title: `${savedChapter.title} Questions`, + type: ChatRoomType.QUESTION, + chapterId: savedChapter.id, + status: ChatRoomStatus.ACTIVE, + }); + await this.chatRoomService.create({ + title: `${savedChapter.title} Discussion`, + type: ChatRoomType.DISCUSSION, + chapterId: savedChapter.id, + status: ChatRoomStatus.ACTIVE, + }); + return savedChapter; + } + + async reorderModules(moduleId: string): Promise { + const modulesToReorder = await this.chapterRepository.find({ + where: { moduleId }, + order: { orderIndex: 'ASC' }, + }); + + for (let i = 0; i < modulesToReorder.length; i++) { + modulesToReorder[i].orderIndex = i + 1; } - async reorderModules(moduleId: string): Promise { - const modulesToReorder = await this.chapterRepository.find({ - where: { moduleId }, - order: { orderIndex: 'ASC' }, - }); + await this.chapterRepository.save(modulesToReorder); + } - for (let i = 0; i < modulesToReorder.length; i++) { - modulesToReorder[i].orderIndex = i + 1; - } + async update( + id: string, + updateChapterDto: UpdateChapterDto, + ): Promise { + const chapter = await this.chapterRepository.findOne({ where: { id } }); - await this.chapterRepository.save(modulesToReorder); + if (!chapter) { + throw new NotFoundException('Chapter not found'); } - - async update( - id: string, - updateChapterDto: UpdateChapterDto, - ): Promise { - const chapter = await this.chapterRepository.findOne({ where: { id } }); - - if (!chapter) { - throw new NotFoundException('Chapter not found'); - } - if (updateChapterDto.orderIndex != null) { - await this.validateOrderIndex(chapter.moduleId, updateChapterDto.orderIndex); - } - if ( - updateChapterDto.orderIndex && - updateChapterDto.orderIndex !== chapter.orderIndex - ) { - const existingChapter = await this.chapterRepository.findOne({ - where: { - moduleId: chapter.moduleId, - orderIndex: updateChapterDto.orderIndex - }, - }); - - if (existingChapter) { - await this.chapterRepository.update(existingChapter.id, { orderIndex: chapter.orderIndex }); - } - } - - Object.assign(chapter, updateChapterDto); - await this.chapterRepository.save(chapter); - - return chapter; + if (updateChapterDto.orderIndex != null) { + await this.validateOrderIndex( + chapter.moduleId, + updateChapterDto.orderIndex, + ); + } + if ( + updateChapterDto.orderIndex && + updateChapterDto.orderIndex !== chapter.orderIndex + ) { + const existingChapter = await this.chapterRepository.findOne({ + where: { + moduleId: chapter.moduleId, + orderIndex: updateChapterDto.orderIndex, + }, + }); + + if (existingChapter) { + await this.chapterRepository.update(existingChapter.id, { + orderIndex: chapter.orderIndex, + }); + } } - async remove(id: string): Promise { - const chapter = await this.chapterRepository.findOne({ where: { id } }); - - if (!chapter) { - throw new BadRequestException('Chapter not found'); - } + Object.assign(chapter, updateChapterDto); + await this.chapterRepository.save(chapter); - const result = await this.chapterRepository.remove(chapter); + return chapter; + } - await this.reorderModules(chapter.moduleId); + async remove(id: string): Promise { + const chapter = await this.chapterRepository.findOne({ where: { id } }); - return result; + if (!chapter) { + throw new BadRequestException('Chapter not found'); } - private async validateOrderIndex(moduleId: string, orderIndex: number): Promise { - const existingModules = await this.chapterRepository.find({ - where: { moduleId }, - order: { orderIndex: 'ASC' }, - }); - if (existingModules.length === 0) { - if (orderIndex !== 1) { - throw new BadRequestException( - 'Order index should be 1 when there are no modules in the course' - ); - } - return; - } - const minIndex = 1; - const maxIndex = existingModules[existingModules.length - 1].orderIndex; - - if (orderIndex < minIndex || orderIndex > maxIndex) { - throw new BadRequestException( - `Order index must be between ${minIndex} and ${maxIndex}` - ); - } + const result = await this.chapterRepository.remove(chapter); + + await this.reorderModules(chapter.moduleId); + + return result; + } + + private async validateOrderIndex( + moduleId: string, + orderIndex: number, + ): Promise { + const existingModules = await this.chapterRepository.find({ + where: { moduleId }, + order: { orderIndex: 'ASC' }, + }); + if (existingModules.length === 0) { + if (orderIndex !== 1) { + throw new BadRequestException( + 'Order index should be 1 when there are no modules in the course', + ); + } + return; } - async validateOwnership(id: string, userId: string): Promise { - const chapter = await this.chapterRepository.findOne({ where: { id }, relations: { module: { course: { teacher: true } } } }); - if (!chapter) throw new NotFoundException('Chapter not found'); - if (chapter.module.course.teacher.id !== userId) - throw new BadRequestException('You can only access your own courses'); + const minIndex = 1; + const maxIndex = existingModules[existingModules.length - 1].orderIndex; + + if (orderIndex < minIndex || orderIndex > maxIndex) { + throw new BadRequestException( + `Order index must be between ${minIndex} and ${maxIndex}`, + ); } - private buildWhereCondition( - userId: string, - role: Role, - baseCondition: FindOptionsWhere = {} - ) { - const conditions: Record< - Role, - () => FindOptionsWhere | FindOptionsWhere[] - > = { - [Role.STUDENT]: () => ({ - ...baseCondition, - module: { - course: { - status: CourseStatus.PUBLISHED, - }, - }, - }), - [Role.TEACHER]: () => [ - { - ...baseCondition, - module: { - course: { - status: CourseStatus.PUBLISHED, - }, - }, - }, - { - ...baseCondition, - module: { - course: { - teacher: { - id: userId, - }, - }, - }, - }, - ], - [Role.ADMIN]: () => baseCondition, - }; - - const buildCondition = conditions[role]; - - if (!buildCondition) { - throw new BadRequestException('Invalid role'); - } - - return buildCondition(); + } + async validateOwnership(id: string, userId: string): Promise { + const chapter = await this.chapterRepository.findOne({ + where: { id }, + relations: { module: { course: { teacher: true } } }, + }); + if (!chapter) throw new NotFoundException('Chapter not found'); + if (chapter.module.course.teacher.id !== userId) + throw new BadRequestException('You can only access your own courses'); + } + private buildWhereCondition( + userId: string, + role: Role, + baseCondition: FindOptionsWhere = {}, + ) { + const conditions: Record< + Role, + () => FindOptionsWhere | FindOptionsWhere[] + > = { + [Role.STUDENT]: () => ({ + ...baseCondition, + module: { + course: { + status: CourseStatus.PUBLISHED, + }, + }, + }), + [Role.TEACHER]: () => [ + { + ...baseCondition, + module: { + course: { + status: CourseStatus.PUBLISHED, + }, + }, + }, + { + ...baseCondition, + module: { + course: { + teacher: { + id: userId, + }, + }, + }, + }, + ], + [Role.ADMIN]: () => baseCondition, + }; + + const buildCondition = conditions[role]; + + if (!buildCondition) { + throw new BadRequestException('Invalid role'); } + return buildCondition(); + } } diff --git a/src/chapter/dtos/chapter-response.dto.ts b/src/chapter/dtos/chapter-response.dto.ts index 5b88151..4ffe5fc 100644 --- a/src/chapter/dtos/chapter-response.dto.ts +++ b/src/chapter/dtos/chapter-response.dto.ts @@ -4,107 +4,107 @@ import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response. import { Chapter } from '../chapter.entity'; export class ChapterResponseDto { - @ApiProperty({ - description: 'Chapter ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }) - id: string; + @ApiProperty({ + description: 'Chapter ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + id: string; - @ApiProperty({ - description: 'Chapter title', - type: String, - example: 'Introduction to Variables', - }) - title: string; + @ApiProperty({ + description: 'Chapter title', + type: String, + example: 'Introduction to Variables', + }) + title: string; - @ApiProperty({ - description: 'Chapter description', - type: String, - example: 'Learn about variables and data types in programming', - }) - description: string; + @ApiProperty({ + description: 'Chapter description', + type: String, + example: 'Learn about variables and data types in programming', + }) + description: string; - @ApiProperty({ - description: 'Chapter video URL', - type: String, - example: 'https://www.youtube.com/watch?v=8k-9mU5KfBQ', - }) - videoUrl: string; + @ApiProperty({ + description: 'Chapter video URL', + type: String, + example: 'https://www.youtube.com/watch?v=8k-9mU5KfBQ', + }) + videoUrl: string; - @ApiProperty({ - description: 'Chapter content', - type: String, - example: 'This chapter covers the basics of programming', - }) - content: string; + @ApiProperty({ + description: 'Chapter content', + type: String, + example: 'This chapter covers the basics of programming', + }) + content: string; - @ApiProperty({ - description: 'Chapter summary', - type: String, - example: 'This chapter is an introduction to programming', - }) - summary: string; + @ApiProperty({ + description: 'Chapter summary', + type: String, + example: 'This chapter is an introduction to programming', + }) + summary: string; - @ApiProperty({ - description: 'Chapter duration', - type: Number, - example: 10, - }) - duration: number; + @ApiProperty({ + description: 'Chapter duration', + type: Number, + example: 10, + }) + duration: number; - @ApiProperty({ - description: 'Chapter module', - type: CourseModuleResponseDto, - example: { - id: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - title: 'Introduction to Programming', - description: 'This module is an introduction to programming', - orderIndex: 1, - courseId: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }, - }) - module: CourseModuleResponseDto; + @ApiProperty({ + description: 'Chapter module', + type: CourseModuleResponseDto, + example: { + id: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + title: 'Introduction to Programming', + description: 'This module is an introduction to programming', + orderIndex: 1, + courseId: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }, + }) + module: CourseModuleResponseDto; - @ApiProperty({ - description: 'Chapter created date', - type: Date, - example: new Date(), - }) - createdAt: Date; + @ApiProperty({ + description: 'Chapter created date', + type: Date, + example: new Date(), + }) + createdAt: Date; - @ApiProperty({ - description: 'Chapter updated date', - type: Date, - example: new Date(), - }) - updatedAt: Date; + @ApiProperty({ + description: 'Chapter updated date', + type: Date, + example: new Date(), + }) + updatedAt: Date; - constructor(chapter: Chapter) { - this.id = chapter.id; - this.title = chapter.title; - this.description = chapter.description; - this.videoUrl = chapter.videoUrl; - this.content = chapter.content; - this.summary = chapter.summary; - this.duration = chapter.duration; - this.createdAt = chapter.createdAt; - this.updatedAt = chapter.updatedAt; - } + constructor(chapter: Chapter) { + this.id = chapter.id; + this.title = chapter.title; + this.description = chapter.description; + this.videoUrl = chapter.videoUrl; + this.content = chapter.content; + this.summary = chapter.summary; + this.duration = chapter.duration; + this.createdAt = chapter.createdAt; + this.updatedAt = chapter.updatedAt; + } } export class PaginatedChapterResponseDto extends PaginatedResponse( - ChapterResponseDto, + ChapterResponseDto, ) { - constructor( - chapters: Chapter[], - total: number, - pageSize: number, - currentPage: number, - ) { - const chapterDtos = chapters.map( - (chapter) => new ChapterResponseDto(chapter), - ); - super(chapterDtos, total, pageSize, currentPage); - } + constructor( + chapters: Chapter[], + total: number, + pageSize: number, + currentPage: number, + ) { + const chapterDtos = chapters.map( + (chapter) => new ChapterResponseDto(chapter), + ); + super(chapterDtos, total, pageSize, currentPage); + } } diff --git a/src/chapter/dtos/create-chapter.dto.ts b/src/chapter/dtos/create-chapter.dto.ts index 06fca66..d2c573f 100644 --- a/src/chapter/dtos/create-chapter.dto.ts +++ b/src/chapter/dtos/create-chapter.dto.ts @@ -1,5 +1,11 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsBoolean, IsNotEmpty, IsNumber, IsString, IsUUID } from 'class-validator'; +import { + IsBoolean, + IsNotEmpty, + IsNumber, + IsString, + IsUUID, +} from 'class-validator'; export class CreateChapterDto { @IsNotEmpty() @@ -56,7 +62,6 @@ export class CreateChapterDto { }) duration: number; - @IsNotEmpty() @IsUUID(4) @ApiProperty({ diff --git a/src/chapter/dtos/update-chapter.dto.ts b/src/chapter/dtos/update-chapter.dto.ts index 78be79a..24b09f4 100644 --- a/src/chapter/dtos/update-chapter.dto.ts +++ b/src/chapter/dtos/update-chapter.dto.ts @@ -4,13 +4,12 @@ import { IsNumber, IsOptional } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; export class UpdateChapterDto extends PartialType(CreateChapterDto) { - - @IsOptional() - @IsNumber() - @ApiProperty({ - description: 'Chapter order index', - type: Number, - example: 1, - }) - orderIndex: number; + @IsOptional() + @IsNumber() + @ApiProperty({ + description: 'Chapter order index', + type: Number, + example: 1, + }) + orderIndex: number; } diff --git a/src/chat-message/chat-message.controller.ts b/src/chat-message/chat-message.controller.ts index cd97bfe..5a5bf35 100644 --- a/src/chat-message/chat-message.controller.ts +++ b/src/chat-message/chat-message.controller.ts @@ -1,104 +1,125 @@ -import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Injectable, Param, ParseUUIDPipe, Patch, Post, Query, Req } from "@nestjs/common"; -import { ChatMessageService } from "./chat-message.service"; -import { ApiTags, ApiBearerAuth, ApiResponse } from "@nestjs/swagger"; -import { PaginateQueryDto } from "src/shared/pagination/dtos/paginate-query.dto"; -import { Roles } from "src/shared/decorators/role.decorator"; -import { Role } from "src/shared/enums"; -import { CreateChatMessageDto, ChatMessageResponseDto } from "./dtos"; -import { AuthenticatedRequest } from "src/auth/interfaces/authenticated-request.interface"; +import { + Body, + Controller, + Delete, + Get, + HttpCode, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, +} from '@nestjs/common'; +import { ChatMessageService } from './chat-message.service'; +import { ApiTags, ApiBearerAuth, ApiResponse } from '@nestjs/swagger'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums'; +import { CreateChatMessageDto, ChatMessageResponseDto } from './dtos'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; @Controller('chat-message') @Injectable() @ApiTags('Chat Message') @ApiBearerAuth() export class ChatMessageController { - constructor( - private readonly chatMessageService: ChatMessageService, - ) { } + constructor(private readonly chatMessageService: ChatMessageService) {} - @Get() - @ApiResponse({ - status: HttpStatus.OK, - description: 'Returns all chat messages', - type: ChatMessageResponseDto, - isArray: true, - }) - @Roles(Role.ADMIN) - async findAll( - @Query() query: PaginateQueryDto, - @Req() request: AuthenticatedRequest, - ) { - return await this.chatMessageService.findAll({ userId: request.user.id, ...query }); - } + @Get() + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all chat messages', + type: ChatMessageResponseDto, + isArray: true, + }) + @Roles(Role.ADMIN) + async findAll( + @Query() query: PaginateQueryDto, + @Req() request: AuthenticatedRequest, + ) { + return await this.chatMessageService.findAll({ + userId: request.user.id, + ...query, + }); + } - @Get(':id') - @ApiResponse({ - status: HttpStatus.OK, - description: 'Returns a chat message by id', - type: ChatMessageResponseDto, - }) - @Roles(Role.ADMIN) - async findOne( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }) - ) id: string, - ) { - return await this.chatMessageService.findOne({ where: { id } }); - } + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns a chat message by id', + type: ChatMessageResponseDto, + }) + @Roles(Role.ADMIN) + async findOne( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ) { + return await this.chatMessageService.findOne({ where: { id } }); + } - @Post() - @ApiResponse({ - status: HttpStatus.CREATED, - description: 'Creates a chat message', - type: ChatMessageResponseDto, - }) - @Roles(Role.ADMIN) - @HttpCode(HttpStatus.CREATED) - async create( - @Body() createChatMessageDto: CreateChatMessageDto, - @Req() request: AuthenticatedRequest - ) { - return await this.chatMessageService.create(request.user.id, createChatMessageDto); - } + @Post() + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Creates a chat message', + type: ChatMessageResponseDto, + }) + @Roles(Role.ADMIN) + @HttpCode(HttpStatus.CREATED) + async create( + @Body() createChatMessageDto: CreateChatMessageDto, + @Req() request: AuthenticatedRequest, + ) { + return await this.chatMessageService.create( + request.user.id, + createChatMessageDto, + ); + } - @Patch(':id') - @ApiResponse({ - status: HttpStatus.OK, - description: 'Updates a chat message by id', - type: ChatMessageResponseDto, - }) - async update( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }) - ) id: string, - @Body() updateChatMessageDto: CreateChatMessageDto, - ) { - return await this.chatMessageService.update({ id }, updateChatMessageDto); - } + @Patch(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Updates a chat message by id', + type: ChatMessageResponseDto, + }) + async update( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @Body() updateChatMessageDto: CreateChatMessageDto, + ) { + return await this.chatMessageService.update({ id }, updateChatMessageDto); + } - @Delete(':id') - @ApiResponse({ - status: HttpStatus.NO_CONTENT, - description: 'Deletes a chat message by id', - }) - @HttpCode(HttpStatus.NO_CONTENT) - async delete( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }) - ) id: string, - ) { - await this.chatMessageService.delete({ id }); - } -} \ No newline at end of file + @Delete(':id') + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Deletes a chat message by id', + }) + @HttpCode(HttpStatus.NO_CONTENT) + async delete( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ) { + await this.chatMessageService.delete({ id }); + } +} diff --git a/src/chat-message/chat-message.entity.ts b/src/chat-message/chat-message.entity.ts index bfc8141..1ce612d 100644 --- a/src/chat-message/chat-message.entity.ts +++ b/src/chat-message/chat-message.entity.ts @@ -1,55 +1,55 @@ -import { - Entity, - PrimaryGeneratedColumn, - Column, - OneToOne, - ManyToOne, - JoinColumn -} from "typeorm"; -import { ChatMessageType } from "./enums/chat-message-type.enum"; -import { ChatRoom } from "src/chat-room/chat-room.entity"; -import { User } from "src/user/user.entity"; +import { + Entity, + PrimaryGeneratedColumn, + Column, + OneToOne, + ManyToOne, + JoinColumn, +} from 'typeorm'; +import { ChatMessageType } from './enums/chat-message-type.enum'; +import { ChatRoom } from 'src/chat-room/chat-room.entity'; +import { User } from 'src/user/user.entity'; @Entity() export class ChatMessage { - @PrimaryGeneratedColumn('uuid') - id: string; + @PrimaryGeneratedColumn('uuid') + id: string; - @Column({ - nullable: false, - }) - content: string; + @Column({ + nullable: false, + }) + content: string; - @OneToOne(() => ChatMessage, { - nullable: true, - onDelete: 'CASCADE', - }) - @JoinColumn({ name: 'reply' }) - reply?: ChatMessage; + @OneToOne(() => ChatMessage, { + nullable: true, + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'reply' }) + reply?: ChatMessage; - @Column({ - nullable: false, - default: false, - }) - isEdited: boolean; + @Column({ + nullable: false, + default: false, + }) + isEdited: boolean; - @Column({ - type: 'enum', - enum: ChatMessageType, - nullable: false, - }) - type: ChatMessageType; + @Column({ + type: 'enum', + enum: ChatMessageType, + nullable: false, + }) + type: ChatMessageType; - @ManyToOne(() => ChatRoom, { - nullable: false, - onDelete: 'CASCADE', - }) - chatRoom: ChatRoom; + @ManyToOne(() => ChatRoom, { + nullable: false, + onDelete: 'CASCADE', + }) + chatRoom: ChatRoom; - @ManyToOne(() => User, { - nullable: false, - onDelete: 'CASCADE', - eager: true, - }) - user: User; -} \ No newline at end of file + @ManyToOne(() => User, { + nullable: false, + onDelete: 'CASCADE', + eager: true, + }) + user: User; +} diff --git a/src/chat-message/chat-message.module.ts b/src/chat-message/chat-message.module.ts index 43ef48a..423df73 100644 --- a/src/chat-message/chat-message.module.ts +++ b/src/chat-message/chat-message.module.ts @@ -1,20 +1,14 @@ -import { Module } from "@nestjs/common"; -import { DatabaseModule } from "src/database/database.module"; -import { TypeOrmModule } from "@nestjs/typeorm"; -import { ChatMessage } from "./chat-message.entity"; -import { ChatMessageController } from "./chat-message.controller"; -import { ChatMessageService } from "./chat-message.service"; -import { chatMessageProviders } from "./chat-message.providers"; +import { Module } from '@nestjs/common'; +import { DatabaseModule } from 'src/database/database.module'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ChatMessage } from './chat-message.entity'; +import { ChatMessageController } from './chat-message.controller'; +import { ChatMessageService } from './chat-message.service'; +import { chatMessageProviders } from './chat-message.providers'; @Module({ - imports: [ - DatabaseModule, - TypeOrmModule.forFeature([ChatMessage]), - ], - controllers: [ChatMessageController], - providers: [ - ...chatMessageProviders, - ChatMessageService, - ], + imports: [DatabaseModule, TypeOrmModule.forFeature([ChatMessage])], + controllers: [ChatMessageController], + providers: [...chatMessageProviders, ChatMessageService], }) -export class ChatMessageModule { } \ No newline at end of file +export class ChatMessageModule {} diff --git a/src/chat-message/chat-message.providers.ts b/src/chat-message/chat-message.providers.ts index 0c53849..5a4515b 100644 --- a/src/chat-message/chat-message.providers.ts +++ b/src/chat-message/chat-message.providers.ts @@ -2,9 +2,10 @@ import { DataSource } from 'typeorm'; import { ChatMessage } from './chat-message.entity'; export const chatMessageProviders = [ - { - provide: 'ChatMessageRepository', - useFactory: (dataSource: DataSource) => dataSource.getRepository(ChatMessage), - inject: ['DataSource'], - }, + { + provide: 'ChatMessageRepository', + useFactory: (dataSource: DataSource) => + dataSource.getRepository(ChatMessage), + inject: ['DataSource'], + }, ]; diff --git a/src/chat-message/chat-message.service.ts b/src/chat-message/chat-message.service.ts index fbe73eb..59d4db2 100644 --- a/src/chat-message/chat-message.service.ts +++ b/src/chat-message/chat-message.service.ts @@ -1,85 +1,98 @@ -import { Injectable, Inject, InternalServerErrorException, NotFoundException } from '@nestjs/common'; +import { + Injectable, + Inject, + InternalServerErrorException, + NotFoundException, +} from '@nestjs/common'; import { Repository, FindOptionsWhere, FindOneOptions } from 'typeorm'; import { ChatMessage } from './chat-message.entity'; import { createPagination } from 'src/shared/pagination'; -import { UpdateChatMessageDto, CreateChatMessageDto, PaginatedChatMessageResponseDto } from './dtos'; +import { + UpdateChatMessageDto, + CreateChatMessageDto, + PaginatedChatMessageResponseDto, +} from './dtos'; @Injectable() export class ChatMessageService { - constructor( - @Inject('ChatMessageRepository') - private readonly chatMessageRepository: Repository, - ) { } + constructor( + @Inject('ChatMessageRepository') + private readonly chatMessageRepository: Repository, + ) {} - async create( - userId: string, - createChatMessageDto: CreateChatMessageDto, - ): Promise { - try { - return await this.chatMessageRepository.save({ - ...createChatMessageDto, - user: { id: userId }, - chatRoomId: { id: createChatMessageDto.chatRoomId }, - reply: createChatMessageDto.replyId - ? { id: createChatMessageDto.replyId } - : null, - }); - } catch (error) { - if (error instanceof Error) throw new InternalServerErrorException(error.message); - } + async create( + userId: string, + createChatMessageDto: CreateChatMessageDto, + ): Promise { + try { + return await this.chatMessageRepository.save({ + ...createChatMessageDto, + user: { id: userId }, + chatRoomId: { id: createChatMessageDto.chatRoomId }, + reply: createChatMessageDto.replyId + ? { id: createChatMessageDto.replyId } + : null, + }); + } catch (error) { + if (error instanceof Error) + throw new InternalServerErrorException(error.message); } + } - async findAll({ - userId, - page = 1, - limit = 20, - search = '', - }: { - userId: string; - page?: number; - limit?: number; - search?: string; - }): Promise { - const { find } = await createPagination(this.chatMessageRepository, { - page, - limit, - }); - const chatMessages = await find({ - where: { content: search, user: { id: userId } }, - }).run(); - return new PaginatedChatMessageResponseDto( - chatMessages.data, - chatMessages.meta.total, - chatMessages.meta.pageSize, - chatMessages.meta.currentPage, - ); - } + async findAll({ + userId, + page = 1, + limit = 20, + search = '', + }: { + userId: string; + page?: number; + limit?: number; + search?: string; + }): Promise { + const { find } = await createPagination(this.chatMessageRepository, { + page, + limit, + }); + const chatMessages = await find({ + where: { content: search, user: { id: userId } }, + }).run(); + return new PaginatedChatMessageResponseDto( + chatMessages.data, + chatMessages.meta.total, + chatMessages.meta.pageSize, + chatMessages.meta.currentPage, + ); + } - async findOne(options: FindOneOptions): Promise { - try { - return await this.chatMessageRepository.findOne(options); - } catch (error) { - if (error instanceof Error) throw new NotFoundException(error.message); - } + async findOne(options: FindOneOptions): Promise { + try { + return await this.chatMessageRepository.findOne(options); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); } + } - async update(criteria: FindOptionsWhere, updateChatMessageDto: UpdateChatMessageDto): Promise { - try { - await this.chatMessageRepository.update(criteria, { - ...updateChatMessageDto, - isEdited: true, - }); - return await this.findOne({ where: criteria }); - } catch (error) { - if (error instanceof Error) throw new NotFoundException(error.message); - } + async update( + criteria: FindOptionsWhere, + updateChatMessageDto: UpdateChatMessageDto, + ): Promise { + try { + await this.chatMessageRepository.update(criteria, { + ...updateChatMessageDto, + isEdited: true, + }); + return await this.findOne({ where: criteria }); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); } + } - async delete(criteria: FindOptionsWhere): Promise { - try { - await this.chatMessageRepository.delete(criteria); - } catch (error) { - if (error instanceof Error) throw new NotFoundException(error.message); - } + async delete(criteria: FindOptionsWhere): Promise { + try { + await this.chatMessageRepository.delete(criteria); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); } + } } diff --git a/src/chat-message/dtos/create-chat-message.dto.ts b/src/chat-message/dtos/create-chat-message.dto.ts index cdf7fa1..3447038 100644 --- a/src/chat-message/dtos/create-chat-message.dto.ts +++ b/src/chat-message/dtos/create-chat-message.dto.ts @@ -1,43 +1,49 @@ -import { IsString, IsNotEmpty, IsOptional, IsUUID, IsEnum } from 'class-validator'; +import { + IsString, + IsNotEmpty, + IsOptional, + IsUUID, + IsEnum, +} from 'class-validator'; import { ChatMessageType } from '../enums/chat-message-type.enum'; import { ApiProperty } from '@nestjs/swagger'; export class CreateChatMessageDto { - @IsString() - @IsNotEmpty() - @ApiProperty({ - description: 'ChatMessage content', - type: String, - example: 'Hello World!', - }) - content: string; + @IsString() + @IsNotEmpty() + @ApiProperty({ + description: 'ChatMessage content', + type: String, + example: 'Hello World!', + }) + content: string; - @IsOptional() - @IsUUID('4') - @ApiProperty({ - description: 'ChatMessage reply ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - required: false, - }) - replyId?: string; + @IsOptional() + @IsUUID('4') + @ApiProperty({ + description: 'ChatMessage reply ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + required: false, + }) + replyId?: string; - @IsEnum(ChatMessageType) - @IsNotEmpty() - @ApiProperty({ - description: 'ChatMessage type', - type: String, - example: ChatMessageType.TEXT, - enum: ChatMessageType, - }) - type: ChatMessageType; + @IsEnum(ChatMessageType) + @IsNotEmpty() + @ApiProperty({ + description: 'ChatMessage type', + type: String, + example: ChatMessageType.TEXT, + enum: ChatMessageType, + }) + type: ChatMessageType; - @IsNotEmpty() - @IsUUID('4') - @ApiProperty({ - description: 'ChatRoom ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }) - chatRoomId: string; -} \ No newline at end of file + @IsNotEmpty() + @IsUUID('4') + @ApiProperty({ + description: 'ChatRoom ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + chatRoomId: string; +} diff --git a/src/chat-message/dtos/index.ts b/src/chat-message/dtos/index.ts index 5f187a6..0b1efc7 100644 --- a/src/chat-message/dtos/index.ts +++ b/src/chat-message/dtos/index.ts @@ -1,4 +1,4 @@ export * from './create-chat-message.dto'; export * from './paginated-chat-message-response.dto'; export * from './update-chat-message.dto'; -export * from './create-chat-message.dto'; \ No newline at end of file +export * from './create-chat-message.dto'; diff --git a/src/chat-message/dtos/paginated-chat-message-response.dto.ts b/src/chat-message/dtos/paginated-chat-message-response.dto.ts index 43075f5..24efef1 100644 --- a/src/chat-message/dtos/paginated-chat-message-response.dto.ts +++ b/src/chat-message/dtos/paginated-chat-message-response.dto.ts @@ -5,70 +5,73 @@ import { ChatMessageType } from '../enums/chat-message-type.enum'; import { UserResponseDto } from 'src/user/dtos/user-response.dto'; export class ChatMessageResponseDto { - @ApiProperty({ - description: 'ChatMessage ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }) - id: string; + @ApiProperty({ + description: 'ChatMessage ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + id: string; - @ApiProperty({ - description: 'ChatMessage content', - type: String, - example: 'Hello World!', - }) - content: string; + @ApiProperty({ + description: 'ChatMessage content', + type: String, + example: 'Hello World!', + }) + content: string; - @ApiProperty({ - description: 'ChatMessage type', - type: String, - example: ChatMessageType.TEXT, - enum: ChatMessageType, - }) - type: ChatMessageType; + @ApiProperty({ + description: 'ChatMessage type', + type: String, + example: ChatMessageType.TEXT, + enum: ChatMessageType, + }) + type: ChatMessageType; - @ApiProperty({ - description: 'ChatMessage user', - type: UserResponseDto, - }) - user: UserResponseDto; + @ApiProperty({ + description: 'ChatMessage user', + type: UserResponseDto, + }) + user: UserResponseDto; - @ApiProperty({ - description: 'ChatRoom ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }) - chatRoomId: string; + @ApiProperty({ + description: 'ChatRoom ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + chatRoomId: string; - @ApiProperty({ - description: 'ChatMessage reply', - type: ChatMessageResponseDto, - required: false, - example: ChatMessageResponseDto, - }) - reply?: ChatMessageResponseDto; + @ApiProperty({ + description: 'ChatMessage reply', + type: ChatMessageResponseDto, + required: false, + example: ChatMessageResponseDto, + }) + reply?: ChatMessageResponseDto; - - constructor(chatMessage: ChatMessage) { - this.id = chatMessage.id; - this.content = chatMessage.content; - this.type = chatMessage.type; - this.user = new UserResponseDto(chatMessage.user); - this.chatRoomId = chatMessage.chatRoom.id; - this.reply = chatMessage.reply ? new ChatMessageResponseDto(chatMessage.reply) : null; - } + constructor(chatMessage: ChatMessage) { + this.id = chatMessage.id; + this.content = chatMessage.content; + this.type = chatMessage.type; + this.user = new UserResponseDto(chatMessage.user); + this.chatRoomId = chatMessage.chatRoom.id; + this.reply = chatMessage.reply + ? new ChatMessageResponseDto(chatMessage.reply) + : null; + } } export class PaginatedChatMessageResponseDto extends PaginatedResponse( - ChatMessageResponseDto, + ChatMessageResponseDto, ) { - constructor( - chatMessages: ChatMessage[], - total: number, - pageSize: number, - currentPage: number, - ) { - const chatMessageDtos = chatMessages.map((chatMessage) => new ChatMessageResponseDto(chatMessage)); - super(chatMessageDtos, total, pageSize, currentPage); - } + constructor( + chatMessages: ChatMessage[], + total: number, + pageSize: number, + currentPage: number, + ) { + const chatMessageDtos = chatMessages.map( + (chatMessage) => new ChatMessageResponseDto(chatMessage), + ); + super(chatMessageDtos, total, pageSize, currentPage); + } } diff --git a/src/chat-message/dtos/update-chat-message.dto.ts b/src/chat-message/dtos/update-chat-message.dto.ts index c8e9f94..da73e49 100644 --- a/src/chat-message/dtos/update-chat-message.dto.ts +++ b/src/chat-message/dtos/update-chat-message.dto.ts @@ -1,24 +1,30 @@ -import { IsString, IsNotEmpty, IsOptional, IsUUID, IsEnum } from 'class-validator'; +import { + IsString, + IsNotEmpty, + IsOptional, + IsUUID, + IsEnum, +} from 'class-validator'; import { ChatMessageType } from '../enums/chat-message-type.enum'; import { ApiProperty } from '@nestjs/swagger'; export class UpdateChatMessageDto { - @IsString() - @IsNotEmpty() - @ApiProperty({ - description: 'ChatMessage content', - type: String, - example: 'Hello World!', - }) - content: string; + @IsString() + @IsNotEmpty() + @ApiProperty({ + description: 'ChatMessage content', + type: String, + example: 'Hello World!', + }) + content: string; - @IsEnum(ChatMessageType) - @IsNotEmpty() - @ApiProperty({ - description: 'ChatMessage type', - type: String, - example: ChatMessageType.TEXT, - enum: ChatMessageType, - }) - type: ChatMessageType; -} \ No newline at end of file + @IsEnum(ChatMessageType) + @IsNotEmpty() + @ApiProperty({ + description: 'ChatMessage type', + type: String, + example: ChatMessageType.TEXT, + enum: ChatMessageType, + }) + type: ChatMessageType; +} diff --git a/src/chat-message/enums/chat-message-type.enum.ts b/src/chat-message/enums/chat-message-type.enum.ts index a0e521c..d0f64f5 100644 --- a/src/chat-message/enums/chat-message-type.enum.ts +++ b/src/chat-message/enums/chat-message-type.enum.ts @@ -1,5 +1,5 @@ export enum ChatMessageType { - TEXT = 'TEXT', - CODE = 'CODE', - IMAGE = 'IMAGE', -} \ No newline at end of file + TEXT = 'TEXT', + CODE = 'CODE', + IMAGE = 'IMAGE', +} diff --git a/src/chat-room/chat-room.controller.ts b/src/chat-room/chat-room.controller.ts index 6b1cfa1..c9236dc 100644 --- a/src/chat-room/chat-room.controller.ts +++ b/src/chat-room/chat-room.controller.ts @@ -1,138 +1,163 @@ -import { Controller, Injectable, Query, HttpStatus, Param, ParseUUIDPipe, Post, HttpCode, Body, Patch, Delete, UseGuards } from "@nestjs/common"; -import { Get } from "@nestjs/common"; -import { ChatRoomService } from "./chat-room.service"; -import { PaginateQueryDto } from "src/shared/pagination/dtos/paginate-query.dto"; -import { ApiTags, ApiBearerAuth, ApiResponse, ApiQuery, ApiParam } from "@nestjs/swagger"; -import { ChatRoomResponseDto, PaginatedChatRoomResponseDto } from "./dtos/paginated-chat-room-response.dto"; -import { Roles } from "src/shared/decorators/role.decorator"; -import { Role } from "src/shared/enums"; -import { CreateChatRoomDto } from "./dtos/create-chat-room.dto"; -import { UpdateChatRoomDto } from "./dtos/update-chat-room.dto"; -import { ChatRoomOwnershipGuard } from "./guards/chat-room-ownership.guard"; +import { + Controller, + Injectable, + Query, + HttpStatus, + Param, + ParseUUIDPipe, + Post, + HttpCode, + Body, + Patch, + Delete, + UseGuards, +} from '@nestjs/common'; +import { Get } from '@nestjs/common'; +import { ChatRoomService } from './chat-room.service'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { + ApiTags, + ApiBearerAuth, + ApiResponse, + ApiQuery, + ApiParam, +} from '@nestjs/swagger'; +import { + ChatRoomResponseDto, + PaginatedChatRoomResponseDto, +} from './dtos/paginated-chat-room-response.dto'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums'; +import { CreateChatRoomDto } from './dtos/create-chat-room.dto'; +import { UpdateChatRoomDto } from './dtos/update-chat-room.dto'; +import { ChatRoomOwnershipGuard } from './guards/chat-room-ownership.guard'; @Controller('chat-room') @Injectable() @ApiTags('Chat Room') @ApiBearerAuth() -export class ChatRoomController { - constructor( - private readonly chatRoomService: ChatRoomService, - ) { } +export class ChatRoomController { + constructor(private readonly chatRoomService: ChatRoomService) {} - @Get() - @ApiResponse({ - status: HttpStatus.OK, - description: 'Get all chat rooms', - type: PaginatedChatRoomResponseDto, - }) - @ApiQuery({ - name: 'page', - type: Number, - required: false, - }) - @ApiQuery({ - name: 'limit', - type: Number, - required: false, - }) - @ApiQuery({ - name: 'search', - type: String, - required: false, - }) - @Roles(Role.ADMIN) - async findAll( - @Query() query: PaginateQueryDto - ) { - return await this.chatRoomService.findAll(query); - } + @Get() + @ApiResponse({ + status: HttpStatus.OK, + description: 'Get all chat rooms', + type: PaginatedChatRoomResponseDto, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + }) + @Roles(Role.ADMIN) + async findAll(@Query() query: PaginateQueryDto) { + return await this.chatRoomService.findAll(query); + } - @Get(':id') - @ApiResponse({ - status: HttpStatus.OK, - description: 'Get chat room by id', - type: ChatRoomResponseDto, - }) - @ApiParam({ - name: 'id', - type: String, - required: true, - }) - @Roles(Role.ADMIN, Role.STUDENT) - @UseGuards(ChatRoomOwnershipGuard) - async findById( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }) - ) id: string - ) { - return new ChatRoomResponseDto(await this.chatRoomService.findOne({ where: { id } })); - } + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Get chat room by id', + type: ChatRoomResponseDto, + }) + @ApiParam({ + name: 'id', + type: String, + required: true, + }) + @Roles(Role.ADMIN, Role.STUDENT) + @UseGuards(ChatRoomOwnershipGuard) + async findById( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ) { + return new ChatRoomResponseDto( + await this.chatRoomService.findOne({ where: { id } }), + ); + } - @Post() - @ApiResponse({ - status: HttpStatus.CREATED, - description: 'Create chat room', - type: ChatRoomResponseDto, - }) - @Roles(Role.ADMIN) - @HttpCode(HttpStatus.CREATED) - async create( - @Body() createChatRoomDto: CreateChatRoomDto, - ) { - return new ChatRoomResponseDto(await this.chatRoomService.create(createChatRoomDto)); - } + @Post() + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Create chat room', + type: ChatRoomResponseDto, + }) + @Roles(Role.ADMIN) + @HttpCode(HttpStatus.CREATED) + async create(@Body() createChatRoomDto: CreateChatRoomDto) { + return new ChatRoomResponseDto( + await this.chatRoomService.create(createChatRoomDto), + ); + } - @Patch(':id') - @ApiResponse({ - status: HttpStatus.OK, - description: 'Update chat room', - type: ChatRoomResponseDto, - }) - @ApiParam({ - name: 'id', - type: String, - required: true, - }) - @Roles(Role.ADMIN) - async update( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }) - ) id: string, - @Body() updateChatRoomDto: UpdateChatRoomDto, - ) { - return new ChatRoomResponseDto(await this.chatRoomService.update({ id }, updateChatRoomDto)); - } + @Patch(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Update chat room', + type: ChatRoomResponseDto, + }) + @ApiParam({ + name: 'id', + type: String, + required: true, + }) + @Roles(Role.ADMIN) + async update( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @Body() updateChatRoomDto: UpdateChatRoomDto, + ) { + return new ChatRoomResponseDto( + await this.chatRoomService.update({ id }, updateChatRoomDto), + ); + } - @Delete(':id') - @ApiResponse({ - status: HttpStatus.NO_CONTENT, - description: 'Delete chat room', - type: ChatRoomResponseDto, - }) - @ApiParam({ - name: 'id', - type: String, - required: true, - }) - @Roles(Role.ADMIN) - @HttpCode(HttpStatus.NO_CONTENT) - async delete( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }) - ) id: string, - ) { - return await this.chatRoomService.delete({ id }); - } -} \ No newline at end of file + @Delete(':id') + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Delete chat room', + type: ChatRoomResponseDto, + }) + @ApiParam({ + name: 'id', + type: String, + required: true, + }) + @Roles(Role.ADMIN) + @HttpCode(HttpStatus.NO_CONTENT) + async delete( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ) { + return await this.chatRoomService.delete({ id }); + } +} diff --git a/src/chat-room/chat-room.entity.ts b/src/chat-room/chat-room.entity.ts index 80b6f29..5056230 100644 --- a/src/chat-room/chat-room.entity.ts +++ b/src/chat-room/chat-room.entity.ts @@ -1,54 +1,63 @@ -import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToOne, JoinColumn, ManyToOne } from "typeorm"; -import { Chapter } from "src/chapter/chapter.entity"; -import { ChatRoomType, ChatRoomStatus } from "./enums"; +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + OneToOne, + JoinColumn, + ManyToOne, +} from 'typeorm'; +import { Chapter } from 'src/chapter/chapter.entity'; +import { ChatRoomType, ChatRoomStatus } from './enums'; @Entity() export class ChatRoom { - @PrimaryGeneratedColumn("uuid") - id: string; - - @ManyToOne(() => Chapter, (chapter) => chapter.chatRooms, { - onDelete: "CASCADE", - nullable: false, - eager: true, - }) - @JoinColumn({ name: "chapter_id" }) - chapter: Chapter; - - @Column({ - nullable: false, - }) - title: string; - - @Column({ - nullable: false, - type: "enum", - enum: ChatRoomType, - default: ChatRoomType.QUESTION, - }) - type: ChatRoomType; - - @Column({ - nullable: false, - type: "enum", - enum: ChatRoomStatus, - default: ChatRoomStatus.ACTIVE, - }) - status: ChatRoomStatus; - - @CreateDateColumn({ - type: "timestamp", - }) - createdAt: Date; - - @Column({ - default: 0, - type: "int", - }) - participantCount: number; - - @UpdateDateColumn({ - type: "timestamp", - }) - updatedAt: Date; -} \ No newline at end of file + @PrimaryGeneratedColumn('uuid') + id: string; + + @ManyToOne(() => Chapter, (chapter) => chapter.chatRooms, { + onDelete: 'CASCADE', + nullable: false, + eager: true, + }) + @JoinColumn({ name: 'chapter_id' }) + chapter: Chapter; + + @Column({ + nullable: false, + }) + title: string; + + @Column({ + nullable: false, + type: 'enum', + enum: ChatRoomType, + default: ChatRoomType.QUESTION, + }) + type: ChatRoomType; + + @Column({ + nullable: false, + type: 'enum', + enum: ChatRoomStatus, + default: ChatRoomStatus.ACTIVE, + }) + status: ChatRoomStatus; + + @CreateDateColumn({ + type: 'timestamp', + }) + createdAt: Date; + + @Column({ + default: 0, + type: 'int', + }) + participantCount: number; + + @UpdateDateColumn({ + type: 'timestamp', + }) + updatedAt: Date; +} diff --git a/src/chat-room/chat-room.module.ts b/src/chat-room/chat-room.module.ts index 5a44124..14a682b 100644 --- a/src/chat-room/chat-room.module.ts +++ b/src/chat-room/chat-room.module.ts @@ -1,23 +1,20 @@ -import { Module } from "@nestjs/common"; -import { DatabaseModule } from "src/database/database.module"; -import { ChatRoomController } from "./chat-room.controller"; -import { ChatRoomService } from "./chat-room.service"; -import { TypeOrmModule } from "@nestjs/typeorm"; -import { ChatRoom } from "./chat-room.entity"; -import { chatRoomProviders } from "./chat-room.providers"; -import { EnrollmentModule } from "src/enrollment/enrollment.module"; +import { Module } from '@nestjs/common'; +import { DatabaseModule } from 'src/database/database.module'; +import { ChatRoomController } from './chat-room.controller'; +import { ChatRoomService } from './chat-room.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ChatRoom } from './chat-room.entity'; +import { chatRoomProviders } from './chat-room.providers'; +import { EnrollmentModule } from 'src/enrollment/enrollment.module'; @Module({ - imports: [ - DatabaseModule, - TypeOrmModule.forFeature([ChatRoom]), - EnrollmentModule, - ], - controllers: [ChatRoomController], - providers: [ - ...chatRoomProviders, - ChatRoomService, - ], - exports: [ChatRoomService], + imports: [ + DatabaseModule, + TypeOrmModule.forFeature([ChatRoom]), + EnrollmentModule, + ], + controllers: [ChatRoomController], + providers: [...chatRoomProviders, ChatRoomService], + exports: [ChatRoomService], }) -export class ChatRoomModule { } \ No newline at end of file +export class ChatRoomModule {} diff --git a/src/chat-room/chat-room.service.ts b/src/chat-room/chat-room.service.ts index 8d10281..bc4f4d5 100644 --- a/src/chat-room/chat-room.service.ts +++ b/src/chat-room/chat-room.service.ts @@ -1,86 +1,86 @@ import { - Injectable, - Inject, - InternalServerErrorException, - NotFoundException, + Injectable, + Inject, + InternalServerErrorException, + NotFoundException, } from '@nestjs/common'; import { Repository, FindOneOptions, FindOptionsWhere } from 'typeorm'; import { ChatRoom } from './chat-room.entity'; import { createPagination } from 'src/shared/pagination'; import { - UpdateChatRoomDto, - PaginatedChatRoomResponseDto, - CreateChatRoomDto, + UpdateChatRoomDto, + PaginatedChatRoomResponseDto, + CreateChatRoomDto, } from './dtos'; @Injectable() export class ChatRoomService { - constructor( - @Inject('ChatRoomRepository') - private readonly chatRoomRepository: Repository, - ) { } + constructor( + @Inject('ChatRoomRepository') + private readonly chatRoomRepository: Repository, + ) {} - async create(createChatRoomDto: CreateChatRoomDto): Promise { - try { - return await this.chatRoomRepository.save({ - ...createChatRoomDto, - chapter: { id: createChatRoomDto.chapterId } - }); - } catch (error) { - if (error instanceof Error) - throw new InternalServerErrorException(error.message); - } + async create(createChatRoomDto: CreateChatRoomDto): Promise { + try { + return await this.chatRoomRepository.save({ + ...createChatRoomDto, + chapter: { id: createChatRoomDto.chapterId }, + }); + } catch (error) { + if (error instanceof Error) + throw new InternalServerErrorException(error.message); } + } - async findAll({ - page = 1, - limit = 20, - search = '', - }: { - page?: number; - limit?: number; - search?: string; - }): Promise { - const { find } = await createPagination(this.chatRoomRepository, { - page, - limit, - }); - const chatRooms = await find({ - where: { chapter: { id: search } }, - }).run(); - return new PaginatedChatRoomResponseDto( - chatRooms.data, - chatRooms.meta.total, - chatRooms.meta.pageSize, - chatRooms.meta.currentPage, - ); - } + async findAll({ + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }): Promise { + const { find } = await createPagination(this.chatRoomRepository, { + page, + limit, + }); + const chatRooms = await find({ + where: { chapter: { id: search } }, + }).run(); + return new PaginatedChatRoomResponseDto( + chatRooms.data, + chatRooms.meta.total, + chatRooms.meta.pageSize, + chatRooms.meta.currentPage, + ); + } - async findOne(options: FindOneOptions): Promise { - try { - return await this.chatRoomRepository.findOne(options); - } catch (error) { - if (error instanceof Error) throw new NotFoundException(error.message); - } + async findOne(options: FindOneOptions): Promise { + try { + return await this.chatRoomRepository.findOne(options); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); } + } - async update( - criteria: FindOptionsWhere, - updateChatRoomDto: UpdateChatRoomDto, - ): Promise { - try { - await this.chatRoomRepository.update(criteria, updateChatRoomDto); - return await this.findOne({ where: criteria }); - } catch (error) { - if (error instanceof Error) throw new NotFoundException(error.message); - } + async update( + criteria: FindOptionsWhere, + updateChatRoomDto: UpdateChatRoomDto, + ): Promise { + try { + await this.chatRoomRepository.update(criteria, updateChatRoomDto); + return await this.findOne({ where: criteria }); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); } + } - async delete(criteria: FindOptionsWhere): Promise { - try { - await this.chatRoomRepository.delete(criteria); - } catch (error) { - if (error instanceof Error) throw new NotFoundException(error.message); - } + async delete(criteria: FindOptionsWhere): Promise { + try { + await this.chatRoomRepository.delete(criteria); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); } + } } diff --git a/src/chat-room/dtos/create-chat-room.dto.ts b/src/chat-room/dtos/create-chat-room.dto.ts index c8eb48c..554c50a 100644 --- a/src/chat-room/dtos/create-chat-room.dto.ts +++ b/src/chat-room/dtos/create-chat-room.dto.ts @@ -1,41 +1,41 @@ -import { IsString, IsEnum, IsUUID, IsNotEmpty } from "class-validator"; -import { ChatRoomType, ChatRoomStatus } from "../enums"; -import { ApiProperty } from "@nestjs/swagger"; +import { IsString, IsEnum, IsUUID, IsNotEmpty } from 'class-validator'; +import { ChatRoomType, ChatRoomStatus } from '../enums'; +import { ApiProperty } from '@nestjs/swagger'; export class CreateChatRoomDto { - @IsString() - @IsNotEmpty() - @ApiProperty({ - description: 'ChatRoom title', - type: String, - example: 'Chat Room 1', - }) - title: string; + @IsString() + @IsNotEmpty() + @ApiProperty({ + description: 'ChatRoom title', + type: String, + example: 'Chat Room 1', + }) + title: string; - @IsEnum(ChatRoomType) - @ApiProperty({ - description: 'ChatRoom type', - type: String, - example: ChatRoomType.QUESTION, - enum: ChatRoomType, - }) - type: ChatRoomType; + @IsEnum(ChatRoomType) + @ApiProperty({ + description: 'ChatRoom type', + type: String, + example: ChatRoomType.QUESTION, + enum: ChatRoomType, + }) + type: ChatRoomType; - @IsEnum(ChatRoomStatus) - @ApiProperty({ - description: 'ChatRoom status', - type: String, - example: ChatRoomStatus.ACTIVE, - enum: ChatRoomStatus, - }) - status: ChatRoomStatus; + @IsEnum(ChatRoomStatus) + @ApiProperty({ + description: 'ChatRoom status', + type: String, + example: ChatRoomStatus.ACTIVE, + enum: ChatRoomStatus, + }) + status: ChatRoomStatus; - @IsUUID('4') - @IsNotEmpty() - @ApiProperty({ - description: 'Chapter ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }) - chapterId: string; -} \ No newline at end of file + @IsUUID('4') + @IsNotEmpty() + @ApiProperty({ + description: 'Chapter ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + chapterId: string; +} diff --git a/src/chat-room/dtos/index.ts b/src/chat-room/dtos/index.ts index 85959d1..7b44003 100644 --- a/src/chat-room/dtos/index.ts +++ b/src/chat-room/dtos/index.ts @@ -1,3 +1,3 @@ export * from './create-chat-room.dto'; export * from './paginated-chat-room-response.dto'; -export * from './update-chat-room.dto'; \ No newline at end of file +export * from './update-chat-room.dto'; diff --git a/src/chat-room/dtos/paginated-chat-room-response.dto.ts b/src/chat-room/dtos/paginated-chat-room-response.dto.ts index 4a9dc59..62597ca 100644 --- a/src/chat-room/dtos/paginated-chat-room-response.dto.ts +++ b/src/chat-room/dtos/paginated-chat-room-response.dto.ts @@ -4,62 +4,64 @@ import { ChatRoom } from '../chat-room.entity'; import { ChatRoomType, ChatRoomStatus } from '../enums'; export class ChatRoomResponseDto { - @ApiProperty({ - description: 'ChatRoom ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }) - id: string; + @ApiProperty({ + description: 'ChatRoom ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + id: string; - @ApiProperty({ - description: 'ChatRoom title', - type: String, - example: 'Chat Room 1', - }) - title: string; + @ApiProperty({ + description: 'ChatRoom title', + type: String, + example: 'Chat Room 1', + }) + title: string; - @ApiProperty({ - description: 'ChatRoom status', - type: String, - example: ChatRoomStatus.ACTIVE, - enum: ChatRoomStatus, - }) - status: ChatRoomStatus; + @ApiProperty({ + description: 'ChatRoom status', + type: String, + example: ChatRoomStatus.ACTIVE, + enum: ChatRoomStatus, + }) + status: ChatRoomStatus; - @ApiProperty({ - description: 'ChatRoom type', - type: String, - example: ChatRoomType.QUESTION, - enum: ChatRoomType, - }) - type: ChatRoomType; + @ApiProperty({ + description: 'ChatRoom type', + type: String, + example: ChatRoomType.QUESTION, + enum: ChatRoomType, + }) + type: ChatRoomType; - @ApiProperty({ - description: 'ChatRoom participant count', - type: Number, - example: 5, - }) - paticipantCount: number; + @ApiProperty({ + description: 'ChatRoom participant count', + type: Number, + example: 5, + }) + paticipantCount: number; - constructor(chatRoom: ChatRoom) { - this.id = chatRoom.id; - this.title = chatRoom.title; - this.status = chatRoom.status; - this.type = chatRoom.type; - this.paticipantCount = chatRoom.participantCount; - } + constructor(chatRoom: ChatRoom) { + this.id = chatRoom.id; + this.title = chatRoom.title; + this.status = chatRoom.status; + this.type = chatRoom.type; + this.paticipantCount = chatRoom.participantCount; + } } export class PaginatedChatRoomResponseDto extends PaginatedResponse( - ChatRoomResponseDto, + ChatRoomResponseDto, ) { - constructor( - chatRooms: ChatRoom[], - total: number, - pageSize: number, - currentPage: number, - ) { - const chatRoomDtos = chatRooms.map((chatRoom) => new ChatRoomResponseDto(chatRoom)); - super(chatRoomDtos, total, pageSize, currentPage); - } + constructor( + chatRooms: ChatRoom[], + total: number, + pageSize: number, + currentPage: number, + ) { + const chatRoomDtos = chatRooms.map( + (chatRoom) => new ChatRoomResponseDto(chatRoom), + ); + super(chatRoomDtos, total, pageSize, currentPage); + } } diff --git a/src/chat-room/dtos/update-chat-room.dto.ts b/src/chat-room/dtos/update-chat-room.dto.ts index 88e3f4f..a6262fa 100644 --- a/src/chat-room/dtos/update-chat-room.dto.ts +++ b/src/chat-room/dtos/update-chat-room.dto.ts @@ -1,32 +1,32 @@ -import { IsString, IsEnum, IsNotEmpty } from "class-validator"; -import { ChatRoomType, ChatRoomStatus } from "../enums"; -import { ApiProperty } from "@nestjs/swagger"; +import { IsString, IsEnum, IsNotEmpty } from 'class-validator'; +import { ChatRoomType, ChatRoomStatus } from '../enums'; +import { ApiProperty } from '@nestjs/swagger'; export class UpdateChatRoomDto { - @IsString() - @IsNotEmpty() - @ApiProperty({ - description: 'ChatRoom name', - type: String, - example: 'Chat Room 1', - }) - name: string; + @IsString() + @IsNotEmpty() + @ApiProperty({ + description: 'ChatRoom name', + type: String, + example: 'Chat Room 1', + }) + name: string; - @IsEnum(ChatRoomType) - @ApiProperty({ - description: 'ChatRoom type', - type: String, - example: ChatRoomType.QUESTION, - enum: ChatRoomType, - }) - type: ChatRoomType; + @IsEnum(ChatRoomType) + @ApiProperty({ + description: 'ChatRoom type', + type: String, + example: ChatRoomType.QUESTION, + enum: ChatRoomType, + }) + type: ChatRoomType; - @IsEnum(ChatRoomStatus) - @ApiProperty({ - description: 'ChatRoom status', - type: String, - example: ChatRoomStatus.ACTIVE, - enum: ChatRoomStatus, - }) - status: ChatRoomStatus; -} \ No newline at end of file + @IsEnum(ChatRoomStatus) + @ApiProperty({ + description: 'ChatRoom status', + type: String, + example: ChatRoomStatus.ACTIVE, + enum: ChatRoomStatus, + }) + status: ChatRoomStatus; +} diff --git a/src/chat-room/enums/chat-room-status.enum.ts b/src/chat-room/enums/chat-room-status.enum.ts index 61983f1..17c1404 100644 --- a/src/chat-room/enums/chat-room-status.enum.ts +++ b/src/chat-room/enums/chat-room-status.enum.ts @@ -1,5 +1,5 @@ export enum ChatRoomStatus { - ACTIVE = 'active', - CLOSED = 'closed', - ARCHIVED = 'archived', -} \ No newline at end of file + ACTIVE = 'active', + CLOSED = 'closed', + ARCHIVED = 'archived', +} diff --git a/src/chat-room/enums/chat-room-type.enum.ts b/src/chat-room/enums/chat-room-type.enum.ts index 1288f28..ba3c594 100644 --- a/src/chat-room/enums/chat-room-type.enum.ts +++ b/src/chat-room/enums/chat-room-type.enum.ts @@ -1,4 +1,4 @@ export enum ChatRoomType { - QUESTION = 'question', - DISCUSSION = 'discussion', -} \ No newline at end of file + QUESTION = 'question', + DISCUSSION = 'discussion', +} diff --git a/src/chat-room/enums/index.ts b/src/chat-room/enums/index.ts index 60c4c3b..0ba0c66 100644 --- a/src/chat-room/enums/index.ts +++ b/src/chat-room/enums/index.ts @@ -1,2 +1,2 @@ export * from './chat-room-status.enum'; -export * from './chat-room-type.enum'; \ No newline at end of file +export * from './chat-room-type.enum'; diff --git a/src/chat-room/guards/chat-room-ownership.guard.ts b/src/chat-room/guards/chat-room-ownership.guard.ts index 6f7554a..ea82d48 100644 --- a/src/chat-room/guards/chat-room-ownership.guard.ts +++ b/src/chat-room/guards/chat-room-ownership.guard.ts @@ -1,8 +1,8 @@ import { - Injectable, - CanActivate, - ExecutionContext, - NotFoundException, + Injectable, + CanActivate, + ExecutionContext, + NotFoundException, } from '@nestjs/common'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { ChatRoomService } from '../chat-room.service'; @@ -10,25 +10,27 @@ import { EnrollmentService } from 'src/enrollment/enrollment.service'; @Injectable() export class ChatRoomOwnershipGuard implements CanActivate { - constructor( - private readonly chatRoomService: ChatRoomService, - private readonly enrollmentRepository: EnrollmentService, - ) { } + constructor( + private readonly chatRoomService: ChatRoomService, + private readonly enrollmentRepository: EnrollmentService, + ) {} - async canActivate(context: ExecutionContext): Promise { - const request: AuthenticatedRequest = context.switchToHttp().getRequest(); - const userId = request.user.id; - const chatRoomId = request.params.id; + async canActivate(context: ExecutionContext): Promise { + const request: AuthenticatedRequest = context.switchToHttp().getRequest(); + const userId = request.user.id; + const chatRoomId = request.params.id; - const chatRoom = await this.chatRoomService.findOne({ where: { id: chatRoomId } }); - const course = chatRoom.chapter.module.course; - const enrollment = await this.enrollmentRepository.findOne({ - user: { id: userId }, - course: { id: course.id }, - }); - if (!enrollment) - throw new NotFoundException('You are not enrolled in this course'); + const chatRoom = await this.chatRoomService.findOne({ + where: { id: chatRoomId }, + }); + const course = chatRoom.chapter.module.course; + const enrollment = await this.enrollmentRepository.findOne({ + user: { id: userId }, + course: { id: course.id }, + }); + if (!enrollment) + throw new NotFoundException('You are not enrolled in this course'); - return true; - } -} \ No newline at end of file + return true; + } +} diff --git a/src/course-module/course-module.controller.ts b/src/course-module/course-module.controller.ts index 27e8451..5a12b31 100644 --- a/src/course-module/course-module.controller.ts +++ b/src/course-module/course-module.controller.ts @@ -37,7 +37,10 @@ import { Roles } from 'src/shared/decorators/role.decorator'; @ApiTags('Course Modules') @ApiBearerAuth() export class CourseModuleController { - constructor(private readonly courseModuleService: CourseModuleService, private readonly courseService: CourseService) { } + constructor( + private readonly courseModuleService: CourseModuleService, + private readonly courseService: CourseService, + ) {} @Get() @ApiResponse({ @@ -92,7 +95,11 @@ export class CourseModuleController { @Req() request: AuthenticatedRequest, @Param('id', new ParseUUIDPipe()) id: string, ): Promise { - return this.courseModuleService.findOne(request.user.id, request.user.role, {where: { id }}); + return this.courseModuleService.findOne( + request.user.id, + request.user.role, + { where: { id } }, + ); } @Get('course/:courseId') @@ -127,7 +134,10 @@ export class CourseModuleController { @Body() createCourseModuleDto: CreateCourseModuleDto, ): Promise { if (createCourseModuleDto.courseId != null) { - await this.courseService.validateOwnership(createCourseModuleDto.courseId, request.user.id); + await this.courseService.validateOwnership( + createCourseModuleDto.courseId, + request.user.id, + ); } return this.courseModuleService.create(createCourseModuleDto); @@ -139,7 +149,7 @@ export class CourseModuleController { type: String, description: 'Course Module ID', }) - @CourseOwnership({adminDraftOnly: true}) + @CourseOwnership({ adminDraftOnly: true }) async update( @Req() request: AuthenticatedRequest, @Param( @@ -153,7 +163,10 @@ export class CourseModuleController { @Body() updateCourseModuleDto: UpdateCourseModuleDto, ): Promise { if (updateCourseModuleDto.courseId != null) { - await this.courseService.validateOwnership(updateCourseModuleDto.courseId, request.user.id); + await this.courseService.validateOwnership( + updateCourseModuleDto.courseId, + request.user.id, + ); } return this.courseModuleService.update(id, updateCourseModuleDto); } diff --git a/src/course-module/course-module.module.ts b/src/course-module/course-module.module.ts index 32eaafc..0cfdf1c 100644 --- a/src/course-module/course-module.module.ts +++ b/src/course-module/course-module.module.ts @@ -7,13 +7,13 @@ import { CourseModule } from 'src/course/course.module'; import { TypeOrmModule } from '@nestjs/typeorm'; @Module({ - imports: [ - DatabaseModule, - CourseModule, - TypeOrmModule.forFeature([CourseModule]), - ], - controllers: [CourseModuleController], - providers: [...courseModuleProviders, CourseModuleService], - exports: [CourseModuleService], + imports: [ + DatabaseModule, + CourseModule, + TypeOrmModule.forFeature([CourseModule]), + ], + controllers: [CourseModuleController], + providers: [...courseModuleProviders, CourseModuleService], + exports: [CourseModuleService], }) -export class CourseModuleModule { } +export class CourseModuleModule {} diff --git a/src/course-module/course-module.service.ts b/src/course-module/course-module.service.ts index e7e5aca..ce0abc2 100644 --- a/src/course-module/course-module.service.ts +++ b/src/course-module/course-module.service.ts @@ -17,7 +17,7 @@ export class CourseModuleService { constructor( @Inject('CourseModuleRepository') private readonly courseModuleRepository: Repository, - ) { } + ) {} async findAll({ page = 1, @@ -104,8 +104,13 @@ export class CourseModuleService { async create( createCourseModuleDto: CreateCourseModuleDto, ): Promise { - let orderIndex = await this.validateAndGetNextOrderIndex(createCourseModuleDto.courseId); - const courseModule = this.courseModuleRepository.create({ ...createCourseModuleDto, orderIndex: orderIndex }); + let orderIndex = await this.validateAndGetNextOrderIndex( + createCourseModuleDto.courseId, + ); + const courseModule = this.courseModuleRepository.create({ + ...createCourseModuleDto, + orderIndex: orderIndex, + }); await this.courseModuleRepository.save(courseModule); return courseModule; @@ -136,7 +141,10 @@ export class CourseModuleService { throw new BadRequestException('Course module not found'); } if (updateCourseModuleDto.orderIndex != null) { - await this.validateOrderIndex(courseModule.courseId, updateCourseModuleDto.orderIndex); + await this.validateOrderIndex( + courseModule.courseId, + updateCourseModuleDto.orderIndex, + ); } if ( updateCourseModuleDto.orderIndex && @@ -150,7 +158,9 @@ export class CourseModuleService { }); if (existingModule) { - await this.courseModuleRepository.update(existingModule.id, { orderIndex: courseModule.orderIndex }); + await this.courseModuleRepository.update(existingModule.id, { + orderIndex: courseModule.orderIndex, + }); } } @@ -175,7 +185,10 @@ export class CourseModuleService { return result; } - private async validateOrderIndex(courseId: string, orderIndex: number): Promise { + private async validateOrderIndex( + courseId: string, + orderIndex: number, + ): Promise { const existingModules = await this.courseModuleRepository.find({ where: { courseId }, order: { orderIndex: 'ASC' }, @@ -183,7 +196,7 @@ export class CourseModuleService { if (existingModules.length === 0) { if (orderIndex !== 1) { throw new BadRequestException( - 'Order index should be 1 when there are no modules in the course' + 'Order index should be 1 when there are no modules in the course', ); } return; @@ -192,23 +205,23 @@ export class CourseModuleService { const maxIndex = existingModules[existingModules.length - 1].orderIndex; if (orderIndex < minIndex || orderIndex > maxIndex) { - throw new BadRequestException( - `Order index is invalid` - ); + throw new BadRequestException(`Order index is invalid`); } } async validateOwnership(id: string, userId: string): Promise { - const courseModule = await this.courseModuleRepository.findOne({ where: { id }, relations: { course: { teacher: true } } }); - if(!courseModule) throw new NotFoundException('Course Module not found'); + const courseModule = await this.courseModuleRepository.findOne({ + where: { id }, + relations: { course: { teacher: true } }, + }); + if (!courseModule) throw new NotFoundException('Course Module not found'); if (courseModule.course.teacher.id !== userId) throw new BadRequestException('You can only access your own courses'); } private buildWhereCondition( userId: string, role: Role, - baseCondition: FindOptionsWhere = {} - ) - { + baseCondition: FindOptionsWhere = {}, + ) { const conditions: Record< Role, () => FindOptionsWhere | FindOptionsWhere[] @@ -246,4 +259,4 @@ export class CourseModuleService { return buildCondition(); } -} \ No newline at end of file +} diff --git a/src/course-module/dtos/create-course-module.dto.ts b/src/course-module/dtos/create-course-module.dto.ts index 44242ff..0917bda 100644 --- a/src/course-module/dtos/create-course-module.dto.ts +++ b/src/course-module/dtos/create-course-module.dto.ts @@ -18,7 +18,6 @@ export class CreateCourseModuleDto { }) description: string; - @IsNotEmpty() @IsUUID(4) @ApiProperty({ diff --git a/src/course-module/dtos/update-course-module.dto.ts b/src/course-module/dtos/update-course-module.dto.ts index c01c043..8347954 100644 --- a/src/course-module/dtos/update-course-module.dto.ts +++ b/src/course-module/dtos/update-course-module.dto.ts @@ -3,13 +3,12 @@ import { CreateCourseModuleDto } from './create-course-module.dto'; import { IsNumber, IsOptional } from 'class-validator'; export class UpdateCourseModuleDto extends PartialType(CreateCourseModuleDto) { - @IsOptional() - @IsNumber() - @ApiProperty({ - description: 'Course Module order index', - type: Number, - example: 1, - }) - orderIndex?: number; - + @IsOptional() + @IsNumber() + @ApiProperty({ + description: 'Course Module order index', + type: Number, + example: 1, + }) + orderIndex?: number; } diff --git a/src/course/course.controller.ts b/src/course/course.controller.ts index 9e52b20..68dd773 100644 --- a/src/course/course.controller.ts +++ b/src/course/course.controller.ts @@ -43,7 +43,7 @@ export class CourseController { constructor( private readonly courseService: CourseService, private readonly categoryService: CategoryService, - ) { } + ) {} @Get() @ApiResponse({ status: HttpStatus.OK, @@ -139,7 +139,7 @@ export class CourseController { } @Patch(':id') - @CourseOwnership({adminDraftOnly: true}) + @CourseOwnership({ adminDraftOnly: true }) @ApiParam({ name: 'id', type: String, @@ -159,7 +159,8 @@ export class CourseController { version: '4', errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, }), - )id: string, + ) + id: string, ): Promise { const category = await this.categoryService.findOne({ where: { id: updateCourseDto.categoryId }, @@ -169,10 +170,7 @@ export class CourseController { throw new BadRequestException('Category not found'); } - const course = await this.courseService.update( - id, - updateCourseDto, - ); + const course = await this.courseService.update(id, updateCourseDto); return new CourseResponseDto(course); } @@ -194,7 +192,8 @@ export class CourseController { version: '4', errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, }), - ) id: string, + ) + id: string, ): Promise { await this.courseService.delete(id); } diff --git a/src/course/course.module.ts b/src/course/course.module.ts index ecc2e70..2b26e35 100644 --- a/src/course/course.module.ts +++ b/src/course/course.module.ts @@ -8,13 +8,9 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { Course } from './course.entity'; @Module({ - imports: [ - DatabaseModule, - CategoryModule, - TypeOrmModule.forFeature([Course]), -], + imports: [DatabaseModule, CategoryModule, TypeOrmModule.forFeature([Course])], controllers: [CourseController], providers: [...courseProviders, CourseService], exports: [CourseService], }) -export class CourseModule { } +export class CourseModule {} diff --git a/src/course/course.service.ts b/src/course/course.service.ts index 36fb4f8..d27d128 100644 --- a/src/course/course.service.ts +++ b/src/course/course.service.ts @@ -19,7 +19,7 @@ export class CourseService { constructor( @Inject('CourseRepository') private readonly courseRepository: Repository, - ) { } + ) {} async findAll({ page = 1, @@ -94,10 +94,7 @@ export class CourseService { } } } - async update( - id: string, - updateCourseDto: UpdateCourseDto, - ): Promise { + async update(id: string, updateCourseDto: UpdateCourseDto): Promise { const existingCourse = await this.courseRepository.findOne({ where: { id }, relations: { @@ -183,8 +180,11 @@ export class CourseService { } } async validateOwnership(id: string, userId: string): Promise { - const course = await this.courseRepository.findOne({ where: { id }, relations: { teacher: true } }); - if(!course) throw new NotFoundException('Course not found'); + const course = await this.courseRepository.findOne({ + where: { id }, + relations: { teacher: true }, + }); + if (!course) throw new NotFoundException('Course not found'); if (course.teacher.id !== userId) throw new BadRequestException('You can only access your own courses'); } diff --git a/src/enrollment/enrollment.controller.ts b/src/enrollment/enrollment.controller.ts index 3bea119..f24d2d5 100644 --- a/src/enrollment/enrollment.controller.ts +++ b/src/enrollment/enrollment.controller.ts @@ -1,33 +1,28 @@ import { - BadRequestException, - Body, - Controller, - Delete, - Get, - HttpCode, - HttpStatus, - Injectable, - Param, - ParseUUIDPipe, - Patch, - Post, - Query, - Req, + BadRequestException, + Body, + Controller, + Delete, + Get, + HttpCode, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, } from '@nestjs/common'; -import { - ApiBearerAuth, - ApiParam, - ApiResponse, - ApiTags, -} from '@nestjs/swagger'; +import { ApiBearerAuth, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { Roles } from 'src/shared/decorators/role.decorator'; import { Role } from 'src/shared/enums'; import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { CreateEnrollmentDto } from './dtos/create-enrollment.dto'; import { - EnrollmentResponseDto, - PaginatedEnrollmentResponseDto, + EnrollmentResponseDto, + PaginatedEnrollmentResponseDto, } from './dtos/enrollment-response.dto'; import { UpdateEnrollmentDto } from './dtos/update-enrollment.dto'; import { EnrollmentService } from './enrollment.service'; @@ -37,91 +32,91 @@ import { EnrollmentService } from './enrollment.service'; @ApiBearerAuth() @Injectable() export class EnrollmentController { - constructor(private readonly enrollmentService: EnrollmentService) { } + constructor(private readonly enrollmentService: EnrollmentService) {} - @Get() - @ApiResponse({ - status: HttpStatus.OK, - type: EnrollmentResponseDto, - description: 'Get all enrollments', - isArray: true, - }) - async findAll( - @Query() query: PaginateQueryDto, - ): Promise { - return this.enrollmentService.findAll(query); - } + @Get() + @ApiResponse({ + status: HttpStatus.OK, + type: EnrollmentResponseDto, + description: 'Get all enrollments', + isArray: true, + }) + async findAll( + @Query() query: PaginateQueryDto, + ): Promise { + return this.enrollmentService.findAll(query); + } - @Get(':id') - @ApiParam({ - name: 'id', - type: String, - description: 'Enrollment ID', - }) - @ApiResponse({ - status: HttpStatus.OK, - type: EnrollmentResponseDto, - description: 'Get enrollment by ID', - }) - async findOne( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - ): Promise { - return await this.enrollmentService.findOne({ id }); - } + @Get(':id') + @ApiParam({ + name: 'id', + type: String, + description: 'Enrollment ID', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: EnrollmentResponseDto, + description: 'Get enrollment by ID', + }) + async findOne( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + return await this.enrollmentService.findOne({ id }); + } - @Post() - @Roles(Role.STUDENT) - @ApiResponse({ - status: HttpStatus.CREATED, - type: EnrollmentResponseDto, - description: 'Create enrollment', - }) - async create( - @Body() createEnrollmentDto: CreateEnrollmentDto, - @Req() req: AuthenticatedRequest, - ): Promise { - return await this.enrollmentService.create(createEnrollmentDto); - } + @Post() + @Roles(Role.STUDENT) + @ApiResponse({ + status: HttpStatus.CREATED, + type: EnrollmentResponseDto, + description: 'Create enrollment', + }) + async create( + @Body() createEnrollmentDto: CreateEnrollmentDto, + @Req() req: AuthenticatedRequest, + ): Promise { + return await this.enrollmentService.create(createEnrollmentDto); + } - @Patch(':id') - @Roles(Role.STUDENT) - @ApiParam({ - name: 'id', - type: String, - description: 'Enrollment ID', - }) - @ApiResponse({ - status: HttpStatus.OK, - type: EnrollmentResponseDto, - description: 'Update enrollment by ID', - }) - async update( - @Param('id', ParseUUIDPipe) id: string, - @Body() updateEnrollmentDto: UpdateEnrollmentDto, - ): Promise { - return await this.enrollmentService.update(id, updateEnrollmentDto); - } + @Patch(':id') + @Roles(Role.STUDENT) + @ApiParam({ + name: 'id', + type: String, + description: 'Enrollment ID', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: EnrollmentResponseDto, + description: 'Update enrollment by ID', + }) + async update( + @Param('id', ParseUUIDPipe) id: string, + @Body() updateEnrollmentDto: UpdateEnrollmentDto, + ): Promise { + return await this.enrollmentService.update(id, updateEnrollmentDto); + } - @Delete(':id') - @Roles(Role.STUDENT) - @ApiParam({ - name: 'id', - type: String, - description: 'Enrollment ID', - }) - @ApiResponse({ - status: HttpStatus.NO_CONTENT, - description: 'Delete enrollment by ID', - }) - @HttpCode(HttpStatus.NO_CONTENT) - async remove(@Param('id', ParseUUIDPipe) id: string): Promise { - return await this.enrollmentService.remove(id); - } + @Delete(':id') + @Roles(Role.STUDENT) + @ApiParam({ + name: 'id', + type: String, + description: 'Enrollment ID', + }) + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Delete enrollment by ID', + }) + @HttpCode(HttpStatus.NO_CONTENT) + async remove(@Param('id', ParseUUIDPipe) id: string): Promise { + return await this.enrollmentService.remove(id); + } } diff --git a/src/enrollment/enrollment.service.ts b/src/enrollment/enrollment.service.ts index cf0fdc3..52394fb 100644 --- a/src/enrollment/enrollment.service.ts +++ b/src/enrollment/enrollment.service.ts @@ -1,7 +1,7 @@ import { - BadRequestException, - Injectable, - NotFoundException, + BadRequestException, + Injectable, + NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { createPagination } from 'src/shared/pagination'; @@ -14,74 +14,73 @@ import { EnrollmentStatus } from './enums/enrollment-status.enum'; @Injectable() export class EnrollmentService { - constructor( - @InjectRepository(Enrollment) - private readonly enrollmentRepository: Repository, - ) { } + constructor( + @InjectRepository(Enrollment) + private readonly enrollmentRepository: Repository, + ) {} - async findAll({ - page = 1, - limit = 20, - }: { - page?: number; - limit?: number; - }): Promise { - const { find } = await createPagination(this.enrollmentRepository, { - page, - limit, - }); + async findAll({ + page = 1, + limit = 20, + }: { + page?: number; + limit?: number; + }): Promise { + const { find } = await createPagination(this.enrollmentRepository, { + page, + limit, + }); - const enrollments = await find({ - relations: { - user: true, - course: true, - }, - }).run(); + const enrollments = await find({ + relations: { + user: true, + course: true, + }, + }).run(); - return enrollments; - } + return enrollments; + } - async findOne( - where: FindOptionsWhere, - ): Promise { - const options: FindOneOptions = { - where, relations: { - user: true, - course: true, - } - }; + async findOne(where: FindOptionsWhere): Promise { + const options: FindOneOptions = { + where, + relations: { + user: true, + course: true, + }, + }; - const enrollment = await this.enrollmentRepository.findOne(options); + const enrollment = await this.enrollmentRepository.findOne(options); - if (!enrollment) - throw new NotFoundException('Enrollment not found'); + if (!enrollment) throw new NotFoundException('Enrollment not found'); - return enrollment; - } + return enrollment; + } - async create(createEnrollmentDto: CreateEnrollmentDto): Promise { - const enrollment = this.enrollmentRepository.create(createEnrollmentDto); - await this.enrollmentRepository.save(enrollment); + async create(createEnrollmentDto: CreateEnrollmentDto): Promise { + const enrollment = this.enrollmentRepository.create(createEnrollmentDto); + await this.enrollmentRepository.save(enrollment); - return enrollment; - } + return enrollment; + } - async update( - id: string, - updateEnrollmentDto: UpdateEnrollmentDto, - ): Promise { - const enrollment = await this.findOne({ - status: EnrollmentStatus.ACTIVE, id, - }); - this.enrollmentRepository.merge(enrollment, updateEnrollmentDto); - await this.enrollmentRepository.save(enrollment); - return enrollment; - } + async update( + id: string, + updateEnrollmentDto: UpdateEnrollmentDto, + ): Promise { + const enrollment = await this.findOne({ + status: EnrollmentStatus.ACTIVE, + id, + }); + this.enrollmentRepository.merge(enrollment, updateEnrollmentDto); + await this.enrollmentRepository.save(enrollment); + return enrollment; + } - async remove(id: string): Promise { - const enrollment = await this.findOne({ - id, - }); - await this.enrollmentRepository.remove(enrollment); - } + async remove(id: string): Promise { + const enrollment = await this.findOne({ + id, + }); + await this.enrollmentRepository.remove(enrollment); + } } diff --git a/src/exam/dtos/create-exam.dto.ts b/src/exam/dtos/create-exam.dto.ts index efd75a1..7169f5a 100644 --- a/src/exam/dtos/create-exam.dto.ts +++ b/src/exam/dtos/create-exam.dto.ts @@ -1,74 +1,80 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsBoolean, IsEnum, IsInt, IsNotEmpty, IsOptional } from 'class-validator'; +import { + IsBoolean, + IsEnum, + IsInt, + IsNotEmpty, + IsOptional, +} from 'class-validator'; import { ExamStatus } from 'src/shared/enums'; export class CreateExamDto { - @IsNotEmpty() - @ApiProperty({ - description: 'Course Module ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }) - courseModuleId: string; + @IsNotEmpty() + @ApiProperty({ + description: 'Course Module ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + courseModuleId: string; - @IsNotEmpty() - @ApiProperty({ - description: 'Exam title', - type: String, - example: 'Biology', - }) - title: string; + @IsNotEmpty() + @ApiProperty({ + description: 'Exam title', + type: String, + example: 'Biology', + }) + title: string; - @IsOptional() - @ApiProperty({ - description: 'Exam description', - type: String, - example: 'This course is an introduction to biology', - }) - description?: string; + @IsOptional() + @ApiProperty({ + description: 'Exam description', + type: String, + example: 'This course is an introduction to biology', + }) + description?: string; - @IsNotEmpty() - @IsInt() - @ApiProperty({ - description: 'time limit to do exam.', - type: Number, - example: 20, - }) - timeLimit: number = 20; + @IsNotEmpty() + @IsInt() + @ApiProperty({ + description: 'time limit to do exam.', + type: Number, + example: 20, + }) + timeLimit: number = 20; - @IsNotEmpty() - @IsInt() - @ApiProperty({ - description: 'Score to pass exam.', - type: Number, - example: 50, - }) - passingScore: number; + @IsNotEmpty() + @IsInt() + @ApiProperty({ + description: 'Score to pass exam.', + type: Number, + example: 50, + }) + passingScore: number; - @IsNotEmpty() - @IsInt() - @ApiProperty({ - description: 'Max attempts to do exam.', - type: Number, - example: 1, - }) - maxAttempts: number; + @IsNotEmpty() + @IsInt() + @ApiProperty({ + description: 'Max attempts to do exam.', + type: Number, + example: 1, + }) + maxAttempts: number; - @IsNotEmpty() - @IsBoolean() - @ApiProperty({ - description: 'Shuffle question.', - type: Boolean, - example: false, - }) - shuffleQuestions: boolean = false; + @IsNotEmpty() + @IsBoolean() + @ApiProperty({ + description: 'Shuffle question.', + type: Boolean, + example: false, + }) + shuffleQuestions: boolean = false; - @IsNotEmpty() - @IsEnum(ExamStatus) - @ApiProperty({ - description: 'Exam status', - type: String, - example: ExamStatus.DRAFT, - enum: ExamStatus - }) - status: ExamStatus = ExamStatus.DRAFT; + @IsNotEmpty() + @IsEnum(ExamStatus) + @ApiProperty({ + description: 'Exam status', + type: String, + example: ExamStatus.DRAFT, + enum: ExamStatus, + }) + status: ExamStatus = ExamStatus.DRAFT; } diff --git a/src/exam/dtos/update-exam.dto.ts b/src/exam/dtos/update-exam.dto.ts index 43c79b9..3199396 100644 --- a/src/exam/dtos/update-exam.dto.ts +++ b/src/exam/dtos/update-exam.dto.ts @@ -1,4 +1,4 @@ import { PartialType } from '@nestjs/swagger'; import { CreateExamDto } from './create-exam.dto'; -export class UpdateExamDto extends PartialType(CreateExamDto) { } \ No newline at end of file +export class UpdateExamDto extends PartialType(CreateExamDto) {} diff --git a/src/exam/exam.providers.ts b/src/exam/exam.providers.ts index a48d639..29b0433 100644 --- a/src/exam/exam.providers.ts +++ b/src/exam/exam.providers.ts @@ -1,8 +1,10 @@ -import { DataSource } from "typeorm"; -import { Exam } from "./exam.entity"; +import { DataSource } from 'typeorm'; +import { Exam } from './exam.entity'; -export const examProviders = [{ +export const examProviders = [ + { provide: 'ExamRepository', useFactory: (dataSource: DataSource) => dataSource.getRepository(Exam), inject: ['DataSource'], -}]; \ No newline at end of file + }, +]; diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts index eeb2eaa..38f252b 100644 --- a/src/exam/exam.service.ts +++ b/src/exam/exam.service.ts @@ -61,8 +61,13 @@ export class ExamService { }, }).run(); - return new PaginatedExamResponseDto(exam.data, exam.meta.total, exam.meta.pageSize, exam.meta.currentPage); - } + return new PaginatedExamResponseDto( + exam.data, + exam.meta.total, + exam.meta.pageSize, + exam.meta.currentPage, + ); + } private validateAndCreateCondition( userId: string, diff --git a/src/file/enums/folder.enum.ts b/src/file/enums/folder.enum.ts index 865ebfb..3299080 100644 --- a/src/file/enums/folder.enum.ts +++ b/src/file/enums/folder.enum.ts @@ -1,6 +1,6 @@ export enum Folder { - CHAPTER_VIDEOS = 'chapter-videos', - COURSE_THUMBNAILS = 'course-thumbnails', - PROFILES = 'profiles', - REWARD_THUMBNAILS = 'reward-thumbnails', -} \ No newline at end of file + CHAPTER_VIDEOS = 'chapter-videos', + COURSE_THUMBNAILS = 'course-thumbnails', + PROFILES = 'profiles', + REWARD_THUMBNAILS = 'reward-thumbnails', +} diff --git a/src/file/file.module.ts b/src/file/file.module.ts index d05f82f..0ec74bb 100644 --- a/src/file/file.module.ts +++ b/src/file/file.module.ts @@ -1,8 +1,8 @@ -import { Module } from "@nestjs/common"; -import { FileService } from "./file.service"; +import { Module } from '@nestjs/common'; +import { FileService } from './file.service'; @Module({ - providers: [FileService], - exports: [FileService] + providers: [FileService], + exports: [FileService], }) -export class FileModule { } \ No newline at end of file +export class FileModule {} diff --git a/src/file/file.service.ts b/src/file/file.service.ts index 0193e90..f5f6d11 100644 --- a/src/file/file.service.ts +++ b/src/file/file.service.ts @@ -1,66 +1,86 @@ -import { Injectable, InternalServerErrorException, NotFoundException } from "@nestjs/common"; -import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, GetObjectCommandOutput } from "@aws-sdk/client-s3"; -import { ConfigService } from "@nestjs/config"; -import { GLOBAL_CONFIG } from "src/shared/constants/global-config.constant"; -import { Folder } from "./enums/folder.enum"; -import { NodeJsClient } from "@smithy/types"; +import { + Injectable, + InternalServerErrorException, + NotFoundException, +} from '@nestjs/common'; +import { + S3Client, + PutObjectCommand, + GetObjectCommand, + DeleteObjectCommand, + GetObjectCommandOutput, +} from '@aws-sdk/client-s3'; +import { ConfigService } from '@nestjs/config'; +import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; +import { Folder } from './enums/folder.enum'; +import { NodeJsClient } from '@smithy/types'; @Injectable() export class FileService { - constructor( - private readonly configService: ConfigService, - ) { } + constructor(private readonly configService: ConfigService) {} - private readonly s3Client = new S3Client({ - region: this.configService.getOrThrow(GLOBAL_CONFIG.AWS_REGION), - }) as NodeJsClient; + private readonly s3Client = new S3Client({ + region: this.configService.getOrThrow(GLOBAL_CONFIG.AWS_REGION), + }) as NodeJsClient; - async upload(folder: Folder, key: string, file: Express.Multer.File): Promise { - try { - await this.s3Client.send( - new PutObjectCommand({ - Bucket: this.configService.getOrThrow(GLOBAL_CONFIG.AWS_BUCKET_NAME), - Key: `${folder}/${key}.${file.originalname.split('.').pop()}`, - Body: file.buffer, - }) - ) - } catch (error) { - if (error instanceof Error) - throw new InternalServerErrorException(error.message); - } + async upload( + folder: Folder, + key: string, + file: Express.Multer.File, + ): Promise { + try { + await this.s3Client.send( + new PutObjectCommand({ + Bucket: this.configService.getOrThrow( + GLOBAL_CONFIG.AWS_BUCKET_NAME, + ), + Key: `${folder}/${key}.${file.originalname.split('.').pop()}`, + Body: file.buffer, + }), + ); + } catch (error) { + if (error instanceof Error) + throw new InternalServerErrorException(error.message); } + } - async get(folder: Folder, key: string): Promise { - try { - const result: GetObjectCommandOutput = await this.s3Client.send( - new GetObjectCommand({ - Bucket: this.configService.getOrThrow(GLOBAL_CONFIG.AWS_BUCKET_NAME), - Key: `${folder}/${key}`, - }) - ) - return await result.Body.transformToByteArray(); - } catch (error) { - if (error instanceof Error) - throw new NotFoundException(error.message); - } + async get(folder: Folder, key: string): Promise { + try { + const result: GetObjectCommandOutput = await this.s3Client.send( + new GetObjectCommand({ + Bucket: this.configService.getOrThrow( + GLOBAL_CONFIG.AWS_BUCKET_NAME, + ), + Key: `${folder}/${key}`, + }), + ); + return await result.Body.transformToByteArray(); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); } + } - async delete(folder: Folder, key: string): Promise { - try { - await this.s3Client.send( - new DeleteObjectCommand({ - Bucket: this.configService.getOrThrow(GLOBAL_CONFIG.AWS_BUCKET_NAME), - Key: `${folder}/${key}`, - }) - ) - } catch (error) { - if (error instanceof Error) - throw new NotFoundException(error.message); - } + async delete(folder: Folder, key: string): Promise { + try { + await this.s3Client.send( + new DeleteObjectCommand({ + Bucket: this.configService.getOrThrow( + GLOBAL_CONFIG.AWS_BUCKET_NAME, + ), + Key: `${folder}/${key}`, + }), + ); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); } + } - async update(folder: Folder, key: string, file: Express.Multer.File): Promise { - await this.delete(folder, key); - await this.upload(folder, key, file); - } -} \ No newline at end of file + async update( + folder: Folder, + key: string, + file: Express.Multer.File, + ): Promise { + await this.delete(folder, key); + await this.upload(folder, key, file); + } +} diff --git a/src/question/dtos/update-question.dto.ts b/src/question/dtos/update-question.dto.ts index 01daabc..97fa0b0 100644 --- a/src/question/dtos/update-question.dto.ts +++ b/src/question/dtos/update-question.dto.ts @@ -1,4 +1,4 @@ import { PartialType } from '@nestjs/swagger'; import { CreateQuestionDto } from './create-question.dto'; -export class UpdateQuestionDto extends PartialType(CreateQuestionDto) { } \ No newline at end of file +export class UpdateQuestionDto extends PartialType(CreateQuestionDto) {} diff --git a/src/question/question.module.ts b/src/question/question.module.ts index 3f415f7..a68ce4f 100644 --- a/src/question/question.module.ts +++ b/src/question/question.module.ts @@ -1,22 +1,16 @@ -import { Module } from "@nestjs/common"; -import { TypeOrmModule } from "@nestjs/typeorm"; -import { Question } from "./question.entity"; -import { DatabaseModule } from "src/database/database.module"; -import { questionProviders } from "./question.providers"; -import { QuestionController } from "./question.controller"; -import { QuestionService } from "./question.service"; -import { Exam } from "src/exam/exam.entity"; +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Question } from './question.entity'; +import { DatabaseModule } from 'src/database/database.module'; +import { questionProviders } from './question.providers'; +import { QuestionController } from './question.controller'; +import { QuestionService } from './question.service'; +import { Exam } from 'src/exam/exam.entity'; @Module({ - imports: [ - DatabaseModule, - TypeOrmModule.forFeature([Question, Exam]), - ], - controllers: [QuestionController], - providers: [ - ...questionProviders, - QuestionService, - ], - exports: [QuestionService] + imports: [DatabaseModule, TypeOrmModule.forFeature([Question, Exam])], + controllers: [QuestionController], + providers: [...questionProviders, QuestionService], + exports: [QuestionService], }) -export class QuestionModule { } \ No newline at end of file +export class QuestionModule {} diff --git a/src/question/question.providers.ts b/src/question/question.providers.ts index f398911..bc5bd59 100644 --- a/src/question/question.providers.ts +++ b/src/question/question.providers.ts @@ -1,8 +1,10 @@ -import { DataSource } from "typeorm"; -import { Question } from "./question.entity"; +import { DataSource } from 'typeorm'; +import { Question } from './question.entity'; -export const questionProviders = [{ +export const questionProviders = [ + { provide: 'QuestionRepository', useFactory: (dataSource: DataSource) => dataSource.getRepository(Question), inject: ['DataSource'], -}]; \ No newline at end of file + }, +]; diff --git a/src/shared/constants/global-config.constant.ts b/src/shared/constants/global-config.constant.ts index 8c851a7..6a59d15 100644 --- a/src/shared/constants/global-config.constant.ts +++ b/src/shared/constants/global-config.constant.ts @@ -12,8 +12,8 @@ export const GLOBAL_CONFIG = { JWT_REFRESH_SECRET: 'JWT_REFRESH_SECRET', JWT_ACCESS_EXPIRATION: 'JWT_ACCESS_EXPIRATION', JWT_REFRESH_EXPIRATION: 'JWT_REFRESH_EXPIRATION', - AWS_ACCESS_KEY_ID: "AWS_ACCESS_KEY_ID", - AWS_SECRET_ACCESS_KEY: "AWS_SECRET_ACCESS_KEY", - AWS_REGION: "AWS_REGION", - AWS_BUCKET_NAME: "AWS_BUCKET_NAME", + AWS_ACCESS_KEY_ID: 'AWS_ACCESS_KEY_ID', + AWS_SECRET_ACCESS_KEY: 'AWS_SECRET_ACCESS_KEY', + AWS_REGION: 'AWS_REGION', + AWS_BUCKET_NAME: 'AWS_BUCKET_NAME', }; diff --git a/src/shared/decorators/course-ownership.decorator.ts b/src/shared/decorators/course-ownership.decorator.ts index ddbe0c9..1c227c8 100644 --- a/src/shared/decorators/course-ownership.decorator.ts +++ b/src/shared/decorators/course-ownership.decorator.ts @@ -3,22 +3,27 @@ import { SetMetadata } from '@nestjs/common'; export const COURSE_OWNERSHIP_KEY = 'courseOwnership' as const; export const ADMIN_DRAFT_ONLY_KEY = 'adminDraftOnly' as const; - type DecoratorFunction = ( target: object | Function, propertyKey?: string | symbol, - descriptor?: PropertyDescriptor + descriptor?: PropertyDescriptor, ) => any; -export const CourseOwnership = (config: {adminDraftOnly?: boolean;} = {}): DecoratorFunction => { +export const CourseOwnership = ( + config: { adminDraftOnly?: boolean } = {}, +): DecoratorFunction => { return ( target: object | Function, propertyKey?: string | symbol, - descriptor?: PropertyDescriptor + descriptor?: PropertyDescriptor, ) => { if (config.adminDraftOnly) { SetMetadata(ADMIN_DRAFT_ONLY_KEY, true)(target, propertyKey, descriptor); } - return SetMetadata(COURSE_OWNERSHIP_KEY, true)(target, propertyKey, descriptor); + return SetMetadata(COURSE_OWNERSHIP_KEY, true)( + target, + propertyKey, + descriptor, + ); }; -}; \ No newline at end of file +}; diff --git a/src/shared/enums/exam-status.enum.ts b/src/shared/enums/exam-status.enum.ts index f3e8c97..0226abc 100644 --- a/src/shared/enums/exam-status.enum.ts +++ b/src/shared/enums/exam-status.enum.ts @@ -1,5 +1,5 @@ export enum ExamStatus { - ARCHIVED = "archived", - DRAFT = "draft", - PUBLISHED = "published", -} \ No newline at end of file + ARCHIVED = 'archived', + DRAFT = 'draft', + PUBLISHED = 'published', +} diff --git a/src/shared/guards/course-ownership.guard.ts b/src/shared/guards/course-ownership.guard.ts index 6a5f7c4..99b715d 100644 --- a/src/shared/guards/course-ownership.guard.ts +++ b/src/shared/guards/course-ownership.guard.ts @@ -1,4 +1,9 @@ -import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; +import { + CanActivate, + ExecutionContext, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { InjectRepository } from '@nestjs/typeorm'; import { Chapter } from 'src/chapter/chapter.entity'; @@ -6,8 +11,10 @@ import { CourseModule } from 'src/course-module/course-module.entity'; import { Course } from 'src/course/course.entity'; import { Repository } from 'typeorm'; import { CourseStatus, Role } from '../enums'; -import { ADMIN_DRAFT_ONLY_KEY, COURSE_OWNERSHIP_KEY } from '../decorators/course-ownership.decorator'; - +import { + ADMIN_DRAFT_ONLY_KEY, + COURSE_OWNERSHIP_KEY, +} from '../decorators/course-ownership.decorator'; type ResourceType = 'course' | 'module' | 'chapter'; @@ -16,7 +23,7 @@ export class CourseOwnershipGuard implements CanActivate { private readonly pathToType: Record = { '/course/': 'course', '/course-module/': 'module', - '/chapter/': 'chapter' + '/chapter/': 'chapter', }; constructor( @@ -26,7 +33,7 @@ export class CourseOwnershipGuard implements CanActivate { @InjectRepository(CourseModule) private moduleRepo: Repository, @InjectRepository(Chapter) - private chapterRepo: Repository + private chapterRepo: Repository, ) {} async canActivate(context: ExecutionContext): Promise { @@ -51,7 +58,7 @@ export class CourseOwnershipGuard implements CanActivate { private isOwnershipRequired(context: ExecutionContext): boolean { return this.reflector.getAllAndOverride(COURSE_OWNERSHIP_KEY, [ context.getHandler(), - context.getClass() + context.getClass(), ]); } @@ -61,34 +68,45 @@ export class CourseOwnershipGuard implements CanActivate { } private async getCourse(path: string, id: string): Promise { - const type = Object.entries(this.pathToType) - .find(([key]) => path.includes(key))?.[1]; - + const type = Object.entries(this.pathToType).find(([key]) => + path.includes(key), + )?.[1]; + if (!type) return null; const queries = { - course: () => this.courseRepo.findOne({ - where: { id }, - relations: { teacher: true } - }), - module: () => this.moduleRepo.findOne({ - where: { id }, - relations: { course: { teacher: true } } - }).then(m => m?.course), - chapter: () => this.chapterRepo.findOne({ - where: { id }, - relations: { module: { course: { teacher: true } } } - }).then(c => c?.module?.course) + course: () => + this.courseRepo.findOne({ + where: { id }, + relations: { teacher: true }, + }), + module: () => + this.moduleRepo + .findOne({ + where: { id }, + relations: { course: { teacher: true } }, + }) + .then((m) => m?.course), + chapter: () => + this.chapterRepo + .findOne({ + where: { id }, + relations: { module: { course: { teacher: true } } }, + }) + .then((c) => c?.module?.course), }; return await queries[type]().catch(() => null); } - private validateAdminAccess(context: ExecutionContext, course: Course): boolean { - const adminDraftOnly = this.reflector.getAllAndOverride(ADMIN_DRAFT_ONLY_KEY, [ - context.getHandler(), - context.getClass() - ]); + private validateAdminAccess( + context: ExecutionContext, + course: Course, + ): boolean { + const adminDraftOnly = this.reflector.getAllAndOverride( + ADMIN_DRAFT_ONLY_KEY, + [context.getHandler(), context.getClass()], + ); if (adminDraftOnly && course.status !== CourseStatus.DRAFT) { throw new UnauthorizedException('Admin can only access draft courses'); @@ -105,4 +123,4 @@ export class CourseOwnershipGuard implements CanActivate { } return true; } -} \ No newline at end of file +} diff --git a/src/shared/pagination/typeorm.ts b/src/shared/pagination/typeorm.ts index b76fec4..5276303 100644 --- a/src/shared/pagination/typeorm.ts +++ b/src/shared/pagination/typeorm.ts @@ -1,61 +1,61 @@ import { BaseEntity, FindManyOptions, Repository } from 'typeorm'; class TypeORMPagination { - constructor( - private readonly repository: Repository, - private readonly queryOptions: FindManyOptions, - private readonly page: number, - private readonly limit: number, - ) { } + constructor( + private readonly repository: Repository, + private readonly queryOptions: FindManyOptions, + private readonly page: number, + private readonly limit: number, + ) {} - protected format(data: [T[], number]) { - const [result, total] = data; - const lastPage = Math.ceil(total / this.limit); - const nextPage = this.page + 1 > lastPage ? null : this.page + 1; - const prevPage = this.page - 1 < 1 ? null : this.page - 1; + protected format(data: [T[], number]) { + const [result, total] = data; + const lastPage = Math.ceil(total / this.limit); + const nextPage = this.page + 1 > lastPage ? null : this.page + 1; + const prevPage = this.page - 1 < 1 ? null : this.page - 1; - const meta = { - total, - pageSize: Math.min(this.limit, total), - currentPage: this.page, - nextPage: nextPage, - prevPage: prevPage, - lastPage: lastPage, - }; + const meta = { + total, + pageSize: Math.min(this.limit, total), + currentPage: this.page, + nextPage: nextPage, + prevPage: prevPage, + lastPage: lastPage, + }; - return { - data: result, - meta, - }; - } + return { + data: result, + meta, + }; + } - async run() { - const data = await this.repository.findAndCount(this.queryOptions); - return this.format(data); - } + async run() { + const data = await this.repository.findAndCount(this.queryOptions); + return this.format(data); + } } type Options = { - readonly page?: number; - readonly limit?: number; + readonly page?: number; + readonly limit?: number; }; export async function createPagination( - repository: Repository, - { page = 1, limit = 20 }: Options, + repository: Repository, + { page = 1, limit = 20 }: Options, ) { - const _take = limit || 10; - const _page = page || 1; - const _skip = (_page - 1) * _take; + const _take = limit || 10; + const _page = page || 1; + const _skip = (_page - 1) * _take; - const find = (options: FindManyOptions) => { - return new TypeORMPagination( - repository, - Object.assign(options, { - take: _take, - skip: _skip, - }), - _page, - _take, - ); - }; - return { find }; + const find = (options: FindManyOptions) => { + return new TypeORMPagination( + repository, + Object.assign(options, { + take: _take, + skip: _skip, + }), + _page, + _take, + ); + }; + return { find }; } diff --git a/src/user-background-topic/user-background-topic.controller.ts b/src/user-background-topic/user-background-topic.controller.ts index 6bdbd73..d27bfbf 100644 --- a/src/user-background-topic/user-background-topic.controller.ts +++ b/src/user-background-topic/user-background-topic.controller.ts @@ -1,23 +1,23 @@ import { - Body, - Controller, - Delete, - Get, - HttpStatus, - Injectable, - Param, - ParseUUIDPipe, - Patch, - Post, - Query, - Req, + Body, + Controller, + Delete, + Get, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, } from '@nestjs/common'; import { - ApiBearerAuth, - ApiParam, - ApiQuery, - ApiResponse, - ApiTags, + ApiBearerAuth, + ApiParam, + ApiQuery, + ApiResponse, + ApiTags, } from '@nestjs/swagger'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { Roles } from 'src/shared/decorators/role.decorator'; @@ -26,8 +26,8 @@ import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto' import { CreateUserBackgroundTopicDto } from './dtos/create-user-background-topic.dto'; import { UpdateUserBackgroundTopicDto } from './dtos/update-user-background-topic.dto'; import { - PaginatedUserBackgroundTopicResponseDto, - UserBackgroundTopicResponseDto, + PaginatedUserBackgroundTopicResponseDto, + UserBackgroundTopicResponseDto, } from './dtos/user-background-response.dto'; import { UserBackgroundTopicService } from './user-background-topic.service'; diff --git a/src/user-background/user-background.service.ts b/src/user-background/user-background.service.ts index 4d77444..21e0df3 100644 --- a/src/user-background/user-background.service.ts +++ b/src/user-background/user-background.service.ts @@ -79,9 +79,8 @@ export class UserBackgroundService { occupationId: data.occupationId, }); - const savedUserBackground = await this.userBackgroundRepository.save( - userBackground, - ); + const savedUserBackground = + await this.userBackgroundRepository.save(userBackground); return savedUserBackground; } @@ -99,9 +98,8 @@ export class UserBackgroundService { }; this.userBackgroundRepository.merge(userBackground, updateData); - const savedUserBackground = await this.userBackgroundRepository.save( - userBackground, - ); + const savedUserBackground = + await this.userBackgroundRepository.save(userBackground); return savedUserBackground; } diff --git a/src/user-streak/user-streak.module.ts b/src/user-streak/user-streak.module.ts index d906409..458d084 100644 --- a/src/user-streak/user-streak.module.ts +++ b/src/user-streak/user-streak.module.ts @@ -7,10 +7,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { UserStreak } from './user-streak.entity'; @Module({ - imports: [ - DatabaseModule, - TypeOrmModule.forFeature([UserStreak]), -], + imports: [DatabaseModule, TypeOrmModule.forFeature([UserStreak])], controllers: [UserStreakController], providers: [...userStreakProviders, UserStreakService], exports: [UserStreakService], diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 8602498..3ac134a 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -1,29 +1,29 @@ import { - Body, - Controller, - Delete, - Get, - HttpCode, - HttpStatus, - Injectable, - Param, - ParseUUIDPipe, - Patch, - Query, - Req, - StreamableFile, - UseInterceptors, - UploadedFile, - ParseFilePipeBuilder, + Body, + Controller, + Delete, + Get, + HttpCode, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Query, + Req, + StreamableFile, + UseInterceptors, + UploadedFile, + ParseFilePipeBuilder, } from '@nestjs/common'; import { - ApiBearerAuth, - ApiBody, - ApiConsumes, - ApiParam, - ApiQuery, - ApiResponse, - ApiTags, + ApiBearerAuth, + ApiBody, + ApiConsumes, + ApiParam, + ApiQuery, + ApiResponse, + ApiTags, } from '@nestjs/swagger'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { Public } from 'src/shared/decorators/public.decorator'; @@ -32,8 +32,8 @@ import { Role } from 'src/shared/enums/roles.enum'; import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { UpdateUserDto } from './dtos/update-user.dto'; import { - PaginatedUserResponseDto, - UserResponseDto, + PaginatedUserResponseDto, + UserResponseDto, } from './dtos/user-response.dto'; import { UserService } from './user.service'; import { FileService } from 'src/file/file.service'; @@ -45,212 +45,212 @@ import { FileInterceptor } from '@nestjs/platform-express'; @ApiTags('User') @Injectable() export class UserController { - constructor( - private readonly userService: UserService, - private readonly fileService: FileService, - ) { } + constructor( + private readonly userService: UserService, + private readonly fileService: FileService, + ) {} - @Get(':id/avatar') - @Public() - @ApiParam({ - name: 'id', - type: String, - description: 'User id', - }) - @ApiResponse({ - status: HttpStatus.OK, - description: 'Get user avatar', - type: StreamableFile, - }) - async getAvatar( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - ): Promise { - const user = await this.userService.findOne({ where: { id } }); - const file = await this.fileService.get( - Folder.PROFILES, user.profileKey); - return new StreamableFile(file, { - disposition: 'inline', - type: `image/${user.profileKey.split('.').pop()}`, - }); - } + @Get(':id/avatar') + @Public() + @ApiParam({ + name: 'id', + type: String, + description: 'User id', + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Get user avatar', + type: StreamableFile, + }) + async getAvatar( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const user = await this.userService.findOne({ where: { id } }); + const file = await this.fileService.get(Folder.PROFILES, user.profileKey); + return new StreamableFile(file, { + disposition: 'inline', + type: `image/${user.profileKey.split('.').pop()}`, + }); + } - @Patch('avatar') - @ApiBearerAuth() - @ApiResponse({ - status: HttpStatus.NO_CONTENT, - description: 'User avatar updated successfully', - }) - @HttpCode(HttpStatus.NO_CONTENT) - @UseInterceptors(FileInterceptor('file')) - @ApiConsumes('multipart/form-data') - @ApiBody({ - schema: { - type: 'object', - properties: { - file: { - type: 'string', - format: 'binary', - }, - }, + @Patch('avatar') + @ApiBearerAuth() + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'User avatar updated successfully', + }) + @HttpCode(HttpStatus.NO_CONTENT) + @UseInterceptors(FileInterceptor('file')) + @ApiConsumes('multipart/form-data') + @ApiBody({ + schema: { + type: 'object', + properties: { + file: { + type: 'string', + format: 'binary', }, - }) - async uploadProfileAvatar( - @Req() request: AuthenticatedRequest, - @UploadedFile( - new ParseFilePipeBuilder() - .addFileTypeValidator({ fileType: 'image/*' }) - .build({ - fileIsRequired: true, - errorHttpStatusCode: HttpStatus.BAD_REQUEST, - }), - ) - file: Express.Multer.File, - ): Promise { - const user = await this.userService.findOne({ - where: { id: request.user.id }, - }) - if (user.profileKey) - await this.fileService.update(Folder.PROFILES, user.id, file); - else { - await this.fileService.upload(Folder.PROFILES, user.id, file); - await this.userService.update(user.id, { profileKey: `${user.id}.${file.mimetype.split('/').pop()}` }); - } + }, + }, + }) + async uploadProfileAvatar( + @Req() request: AuthenticatedRequest, + @UploadedFile( + new ParseFilePipeBuilder() + .addFileTypeValidator({ fileType: 'image/*' }) + .build({ + fileIsRequired: true, + errorHttpStatusCode: HttpStatus.BAD_REQUEST, + }), + ) + file: Express.Multer.File, + ): Promise { + const user = await this.userService.findOne({ + where: { id: request.user.id }, + }); + if (user.profileKey) + await this.fileService.update(Folder.PROFILES, user.id, file); + else { + await this.fileService.upload(Folder.PROFILES, user.id, file); + await this.userService.update(user.id, { + profileKey: `${user.id}.${file.mimetype.split('/').pop()}`, + }); } + } - @Get('avatar') - @ApiBearerAuth() - @ApiResponse({ - status: HttpStatus.OK, - description: 'Get user avatar', - type: StreamableFile, - }) - async getProfileAvatar( - @Req() request: AuthenticatedRequest, - ): Promise { - const user = await this.userService.findOne({ - where: { id: request.user.id }, - }); - const file = await this.fileService.get( - Folder.PROFILES, user.profileKey); - return new StreamableFile(file, { - disposition: 'inline', - type: `image/${user.profileKey.split('.').pop()}`, - }); - } + @Get('avatar') + @ApiBearerAuth() + @ApiResponse({ + status: HttpStatus.OK, + description: 'Get user avatar', + type: StreamableFile, + }) + async getProfileAvatar( + @Req() request: AuthenticatedRequest, + ): Promise { + const user = await this.userService.findOne({ + where: { id: request.user.id }, + }); + const file = await this.fileService.get(Folder.PROFILES, user.profileKey); + return new StreamableFile(file, { + disposition: 'inline', + type: `image/${user.profileKey.split('.').pop()}`, + }); + } - @Get('profile') - @ApiBearerAuth() - @ApiResponse({ - status: HttpStatus.OK, - type: UserResponseDto, - description: 'Get user profile', - }) - async getProfile( - @Req() request: AuthenticatedRequest, - ): Promise { - const user = await this.userService.findOne({ - where: { id: request.user.id }, - }); - return new UserResponseDto(user); - } + @Get('profile') + @ApiBearerAuth() + @ApiResponse({ + status: HttpStatus.OK, + type: UserResponseDto, + description: 'Get user profile', + }) + async getProfile( + @Req() request: AuthenticatedRequest, + ): Promise { + const user = await this.userService.findOne({ + where: { id: request.user.id }, + }); + return new UserResponseDto(user); + } - @Get() - @Roles(Role.ADMIN) - @ApiBearerAuth() - @ApiResponse({ - status: HttpStatus.OK, - type: PaginatedUserResponseDto, - description: 'Get all users', - }) - @ApiQuery({ - name: 'page', - type: Number, - required: false, - description: 'Page number', - }) - @ApiQuery({ - name: 'limit', - type: Number, - required: false, - description: 'Items per page', - }) - @ApiQuery({ - name: 'search', - type: String, - required: false, - description: 'Search by email', - }) - async findAll( - @Query() query: PaginateQueryDto, - ): Promise { - return this.userService.findAll({ - page: query.page, - limit: query.limit, - search: query.search, - }); - } + @Get() + @Roles(Role.ADMIN) + @ApiBearerAuth() + @ApiResponse({ + status: HttpStatus.OK, + type: PaginatedUserResponseDto, + description: 'Get all users', + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by email', + }) + async findAll( + @Query() query: PaginateQueryDto, + ): Promise { + return this.userService.findAll({ + page: query.page, + limit: query.limit, + search: query.search, + }); + } - @Get(':id') - @ApiParam({ - name: 'id', - type: String, - description: 'User id', - }) - @ApiResponse({ - status: HttpStatus.OK, - type: UserResponseDto, - description: 'Get user by id', - }) - @Public() - async findOne( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - ): Promise { - const user = await this.userService.findOne({ where: { id } }); - return new UserResponseDto(user); - } + @Get(':id') + @ApiParam({ + name: 'id', + type: String, + description: 'User id', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: UserResponseDto, + description: 'Get user by id', + }) + @Public() + async findOne( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const user = await this.userService.findOne({ where: { id } }); + return new UserResponseDto(user); + } - @Patch() - @ApiBearerAuth() - @ApiResponse({ - status: HttpStatus.OK, - type: UserResponseDto, - description: 'Update user', - }) - async update( - @Req() request: AuthenticatedRequest, - @Body() updateUserDto: UpdateUserDto, - ): Promise { - if (updateUserDto.password) - updateUserDto.password = await hash(updateUserDto.password); - const user = await this.userService.update(request.user.id, updateUserDto); - return new UserResponseDto(user); - } + @Patch() + @ApiBearerAuth() + @ApiResponse({ + status: HttpStatus.OK, + type: UserResponseDto, + description: 'Update user', + }) + async update( + @Req() request: AuthenticatedRequest, + @Body() updateUserDto: UpdateUserDto, + ): Promise { + if (updateUserDto.password) + updateUserDto.password = await hash(updateUserDto.password); + const user = await this.userService.update(request.user.id, updateUserDto); + return new UserResponseDto(user); + } - @Delete() - @ApiBearerAuth() - @ApiResponse({ - status: HttpStatus.NO_CONTENT, - description: 'Delete user', - }) - @HttpCode(HttpStatus.NO_CONTENT) - async delete( - @Req() request: AuthenticatedRequest, - ): Promise<{ massage: string }> { - await this.userService.delete(request.user.id); - return { massage: 'User deleted successfully' }; - } + @Delete() + @ApiBearerAuth() + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Delete user', + }) + @HttpCode(HttpStatus.NO_CONTENT) + async delete( + @Req() request: AuthenticatedRequest, + ): Promise<{ massage: string }> { + await this.userService.delete(request.user.id); + return { massage: 'User deleted successfully' }; + } } diff --git a/src/user/user.module.ts b/src/user/user.module.ts index 6fedadd..1cd1f2e 100644 --- a/src/user/user.module.ts +++ b/src/user/user.module.ts @@ -6,12 +6,9 @@ import { UserService } from './user.service'; import { FileModule } from 'src/file/file.module'; @Module({ - imports: [ - TypeOrmModule.forFeature([User]), - FileModule, - ], - controllers: [UserController], - providers: [UserService], - exports: [UserService], + imports: [TypeOrmModule.forFeature([User]), FileModule], + controllers: [UserController], + providers: [UserService], + exports: [UserService], }) -export class UserModule { } +export class UserModule {} diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 401f59d..a21cfd7 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,8 +1,8 @@ import { - BadRequestException, - Inject, - Injectable, - NotFoundException, + BadRequestException, + Inject, + Injectable, + NotFoundException, } from '@nestjs/common'; import { createPagination } from 'src/shared/pagination'; import { FindOneOptions, ILike, Repository } from 'typeorm'; @@ -13,60 +13,63 @@ import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity @Injectable() export class UserService { - constructor( - @Inject('UserRepository') - private readonly userRepository: Repository, - ) { } + constructor( + @Inject('UserRepository') + private readonly userRepository: Repository, + ) {} - async findAll({ - page = 1, - limit = 20, - search = '', - }: { - page?: number; - limit?: number; - search?: string; - }): Promise { - const { find } = await createPagination(this.userRepository, { - page, - limit, - }); + async findAll({ + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }): Promise { + const { find } = await createPagination(this.userRepository, { + page, + limit, + }); - const users = await find({ - where: { email: ILike(`%${search}%`) }, - }).run(); + const users = await find({ + where: { email: ILike(`%${search}%`) }, + }).run(); - return users; - } + return users; + } - async findOne(options: FindOneOptions): Promise { - const user = this.userRepository.findOne(options); - if (!user) throw new NotFoundException('User not found'); - return user; - } + async findOne(options: FindOneOptions): Promise { + const user = this.userRepository.findOne(options); + if (!user) throw new NotFoundException('User not found'); + return user; + } - async create(createUserDto: CreateUserDto): Promise { - try { - return this.userRepository.save(createUserDto); - } catch (error) { - if (error instanceof Error) throw new BadRequestException(error.message); - } + async create(createUserDto: CreateUserDto): Promise { + try { + return this.userRepository.save(createUserDto); + } catch (error) { + if (error instanceof Error) throw new BadRequestException(error.message); } + } - async update(id: string, partialEntity: QueryDeepPartialEntity): Promise { - try { - await this.userRepository.update(id, partialEntity); - return await this.findOne({ where: { id } }); - } catch (error) { - if (error instanceof Error) throw new NotFoundException('User not found'); - } + async update( + id: string, + partialEntity: QueryDeepPartialEntity, + ): Promise { + try { + await this.userRepository.update(id, partialEntity); + return await this.findOne({ where: { id } }); + } catch (error) { + if (error instanceof Error) throw new NotFoundException('User not found'); } + } - async delete(id: string): Promise { - try { - await this.userRepository.delete(id); - } catch (error) { - if (error instanceof Error) throw new NotFoundException('User not found'); - } + async delete(id: string): Promise { + try { + await this.userRepository.delete(id); + } catch (error) { + if (error instanceof Error) throw new NotFoundException('User not found'); } + } } From 7417c19113177d937c1fecd5a17d79f0339fc003 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Mon, 18 Nov 2024 16:28:43 +0700 Subject: [PATCH 081/155] fix: url path --- src/exam-answer/exam-answer.controller.ts | 4 ++-- src/exam-attempt/exam-attempt.controller.ts | 2 +- src/question-option/question-option.controller.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/exam-answer/exam-answer.controller.ts b/src/exam-answer/exam-answer.controller.ts index d9dbca5..3fc4264 100644 --- a/src/exam-answer/exam-answer.controller.ts +++ b/src/exam-answer/exam-answer.controller.ts @@ -26,7 +26,7 @@ import { Role } from 'src/shared/enums'; import { CreateExamAnswerDto } from './dtos/create-exam-answer.dto'; import { UpdateExamAnswerDto } from './dtos/update-exam-answer.dto'; -@Controller('examAnswer') +@Controller('exam-answer') @ApiTags('ExamAnswer') @ApiBearerAuth() @Injectable() @@ -155,7 +155,7 @@ export class ExamAnswerController { ); } - @Get('selectedOption/:selectedOptionId') + @Get('selected-option/:selectedOptionId') @Roles(Role.TEACHER) @Roles(Role.ADMIN) @ApiResponse({ diff --git a/src/exam-attempt/exam-attempt.controller.ts b/src/exam-attempt/exam-attempt.controller.ts index d830846..d5f8a01 100644 --- a/src/exam-attempt/exam-attempt.controller.ts +++ b/src/exam-attempt/exam-attempt.controller.ts @@ -26,7 +26,7 @@ import { Role } from 'src/shared/enums'; import { CreateExamAttemptDto } from './dtos/create-exam-attempt.dto'; import { UpdateExamAttemptDto } from './dtos/update-exam-attempt.dto'; -@Controller('examAttempt') +@Controller('exam-attempt') @ApiTags('ExamAttempt') @ApiBearerAuth() @Injectable() diff --git a/src/question-option/question-option.controller.ts b/src/question-option/question-option.controller.ts index c15cf5b..293d2f2 100644 --- a/src/question-option/question-option.controller.ts +++ b/src/question-option/question-option.controller.ts @@ -26,7 +26,7 @@ import { Role } from 'src/shared/enums'; import { CreateQuestionOptionDto } from './dtos/create-question-option.dto'; import { UpdateQuestionOptionDto } from './dtos/update-question-option.dto'; -@Controller('questionOption') +@Controller('question-option') @Injectable() @ApiTags('QuestionOption') @ApiBearerAuth() From f9609f7d2ca1a057018eb863b131d16ff9e77823 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Mon, 18 Nov 2024 18:47:46 +0700 Subject: [PATCH 082/155] feat: add admin email and password to environment configuration; enhance user and chat room DTOs with additional fields --- .env.example | 4 ++- src/auth/auth.service.ts | 9 +++-- src/shared/configs/dotenv.config.ts | 2 ++ .../constants/global-config.constant.ts | 2 ++ src/user/dtos/create-user.dto.ts | 22 ++++++------ src/user/dtos/update-user.dto.ts | 18 +++++++++- src/user/user.controller.ts | 2 +- src/user/user.entity.ts | 16 ++++++--- src/user/user.service.ts | 36 ++++++++++++++----- 9 files changed, 82 insertions(+), 29 deletions(-) diff --git a/.env.example b/.env.example index 4875d03..a9bf602 100644 --- a/.env.example +++ b/.env.example @@ -13,4 +13,6 @@ JWT_REFRESH_EXPIRATION=7d AWS_ACCESS_KEY_ID=youraccesskey AWS_SECRET_ACCESS_KEY=yoursecretkey AWS_REGION=yourregion -AWS_BUCKET_NAME=yourbucketname \ No newline at end of file +AWS_BUCKET_NAME=yourbucketname +ADMIN_EMAIL=admin@gmail.com +ADMIN_PASSWORD=P@ssword! \ No newline at end of file diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index ebd4ca6..70a5d0d 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -15,6 +15,7 @@ import { JwtPayloadDto } from './dtos/jwt-payload.dto'; import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; import { UserResponseDto } from 'src/user/dtos/user-response.dto'; import { UserStreakService } from 'src/user-streak/user-streak.service'; +import { Role } from 'src/shared/enums'; @Injectable() export class AuthService { @@ -38,7 +39,8 @@ export class AuthService { role: user.role, }); const refreshToken = this.generateRefreshToken(); - await this.userStreakService.update(user.id); + if (user.role === Role.STUDENT) + await this.userStreakService.update(user.id); return { accessToken, refreshToken, @@ -66,7 +68,8 @@ export class AuthService { role: createdUser.role, }); const refreshToken = this.generateRefreshToken(); - await this.userStreakService.create(createdUser.id); + if (createdUser.role === Role.STUDENT) + await this.userStreakService.create(createdUser.id); return { accessToken, refreshToken, @@ -74,7 +77,7 @@ export class AuthService { }; } catch (error) { if (error instanceof Error) { - await this.userService.delete(createdUser.id); + await this.userService.delete({ id: createdUser.id }); throw new InternalServerErrorException(error.message); } } diff --git a/src/shared/configs/dotenv.config.ts b/src/shared/configs/dotenv.config.ts index 14f5617..7c8e00c 100644 --- a/src/shared/configs/dotenv.config.ts +++ b/src/shared/configs/dotenv.config.ts @@ -25,4 +25,6 @@ export const dotenvConfig = Joi.object({ AWS_SECRET_ACCESS_KEY: Joi.string().required(), AWS_REGION: Joi.string().required(), AWS_BUCKET_NAME: Joi.string().required(), + ADMIN_EMAIL: Joi.string().required(), + ADMIN_PASSWORD: Joi.string().required(), }); diff --git a/src/shared/constants/global-config.constant.ts b/src/shared/constants/global-config.constant.ts index 6a59d15..74dae04 100644 --- a/src/shared/constants/global-config.constant.ts +++ b/src/shared/constants/global-config.constant.ts @@ -16,4 +16,6 @@ export const GLOBAL_CONFIG = { AWS_SECRET_ACCESS_KEY: 'AWS_SECRET_ACCESS_KEY', AWS_REGION: 'AWS_REGION', AWS_BUCKET_NAME: 'AWS_BUCKET_NAME', + ADMIN_EMAIL: 'ADMIN_EMAIL', + ADMIN_PASSWORD: 'ADMIN_PASSWORD', }; diff --git a/src/user/dtos/create-user.dto.ts b/src/user/dtos/create-user.dto.ts index ca6935f..044d76b 100644 --- a/src/user/dtos/create-user.dto.ts +++ b/src/user/dtos/create-user.dto.ts @@ -4,8 +4,9 @@ import { IsNotEmpty, IsString, IsStrongPassword, - IsNumber, + IsEnum, } from 'class-validator'; +import { AvailableRoles, Role } from 'src/shared/enums'; export class CreateUserDto { @IsEmail() @@ -35,15 +36,6 @@ export class CreateUserDto { }) fullname: string; - @IsNumber() - @IsNotEmpty() - @ApiProperty({ - description: 'points for reward', - type: Number, - example: '200', - }) - points: number; - @IsStrongPassword() @IsNotEmpty() @ApiProperty({ @@ -52,4 +44,14 @@ export class CreateUserDto { example: 'P@ssw0rd!', }) password: string; + + @IsEnum(AvailableRoles) + @IsNotEmpty() + @ApiProperty({ + description: 'User role', + type: String, + example: AvailableRoles.STUDENT, + enum: AvailableRoles, + }) + role: Role; } diff --git a/src/user/dtos/update-user.dto.ts b/src/user/dtos/update-user.dto.ts index e89fde4..1bc1c83 100644 --- a/src/user/dtos/update-user.dto.ts +++ b/src/user/dtos/update-user.dto.ts @@ -1,4 +1,10 @@ -import { IsString, IsOptional, IsStrongPassword } from 'class-validator'; +import { + IsString, + IsOptional, + IsStrongPassword, + IsInt, + Min, +} from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; export class UpdateUserDto { @@ -20,4 +26,14 @@ export class UpdateUserDto { example: 'P@ssw0rd!', }) password?: string; + + @IsInt() + @Min(0) + @IsOptional() + @ApiProperty({ + description: 'User points', + type: Number, + example: 100, + }) + points?: number; } diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 3ac134a..9bfb668 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -250,7 +250,7 @@ export class UserController { async delete( @Req() request: AuthenticatedRequest, ): Promise<{ massage: string }> { - await this.userService.delete(request.user.id); + await this.userService.delete({ id: request.user.id }); return { massage: 'User deleted successfully' }; } } diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index 76a2b55..06670c4 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -11,7 +11,6 @@ import { PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; - import { UserReward } from 'src/userReward/user-reward.entity'; @Entity() @@ -54,23 +53,30 @@ export class User { email: string; @Column({ - nullable: false, + nullable: true, default: 0, }) points: number; - @OneToMany(() => Course, (course) => course.teacher) + @OneToMany(() => Course, (course) => course.teacher, { + nullable: true, + }) courses: Course[]; - @OneToMany(() => Enrollment, (enrollment) => enrollment.user) + @OneToMany(() => Enrollment, (enrollment) => enrollment.user, { + nullable: true, + }) enrollments: Enrollment[]; @OneToMany(() => ExamAttempt, (examAttempt) => examAttempt.exam, { cascade: true, + nullable: true, }) examAttempt: ExamAttempt[]; - @OneToMany(() => UserReward, (userReward) => userReward.user) + @OneToMany(() => UserReward, (userReward) => userReward.user, { + nullable: true, + }) rewards: UserReward[]; @CreateDateColumn({ diff --git a/src/user/user.service.ts b/src/user/user.service.ts index a21cfd7..ea34624 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -3,21 +3,43 @@ import { Inject, Injectable, NotFoundException, + OnModuleInit, } from '@nestjs/common'; import { createPagination } from 'src/shared/pagination'; -import { FindOneOptions, ILike, Repository } from 'typeorm'; +import { FindOneOptions, ILike, Repository, FindOptionsWhere } from 'typeorm'; import { CreateUserDto } from './dtos/create-user.dto'; import { PaginatedUserResponseDto } from './dtos/user-response.dto'; import { User } from './user.entity'; import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; +import { ConfigService } from '@nestjs/config'; +import { Role } from 'src/shared/enums'; +import { hash } from 'argon2'; @Injectable() -export class UserService { +export class UserService implements OnModuleInit { constructor( @Inject('UserRepository') private readonly userRepository: Repository, + private readonly configService: ConfigService, ) {} + async onModuleInit() { + const adminEmail = this.configService.get('ADMIN_EMAIL'); + const adminPassword = this.configService.get('ADMIN_PASSWORD'); + const admin = await this.userRepository.findOne({ + where: { email: adminEmail }, + }); + if (!admin) { + await this.userRepository.save({ + email: adminEmail, + password: await hash(adminPassword), + fullname: 'Admin', + role: Role.ADMIN, + username: 'admin', + }); + } + } + async findAll({ page = 1, limit = 20, @@ -40,14 +62,12 @@ export class UserService { } async findOne(options: FindOneOptions): Promise { - const user = this.userRepository.findOne(options); - if (!user) throw new NotFoundException('User not found'); - return user; + return await this.userRepository.findOne(options); } async create(createUserDto: CreateUserDto): Promise { try { - return this.userRepository.save(createUserDto); + return await this.userRepository.save(createUserDto); } catch (error) { if (error instanceof Error) throw new BadRequestException(error.message); } @@ -65,9 +85,9 @@ export class UserService { } } - async delete(id: string): Promise { + async delete(criteria: FindOptionsWhere): Promise { try { - await this.userRepository.delete(id); + await this.userRepository.delete(criteria); } catch (error) { if (error instanceof Error) throw new NotFoundException('User not found'); } From 59d3ee4d2501bf6c64b1b117186ad1ce2729c51d Mon Sep 17 00:00:00 2001 From: khris-xp Date: Mon, 18 Nov 2024 20:14:51 +0700 Subject: [PATCH 083/155] feat: roadmap module service --- src/app.module.ts | 10 +- src/course/course.entity.ts | 6 + src/roadmap/dtos/create-roadmap.dto.ts | 31 +++++ src/roadmap/dtos/index.ts | 3 + src/roadmap/dtos/roadmap-response.dto.ts | 77 +++++++++++++ src/roadmap/dtos/update-roadmap.dto.ts | 4 + src/roadmap/roadmap.controller.ts | 138 +++++++++++++++++++++++ src/roadmap/roadmap.entity.ts | 52 +++++++++ src/roadmap/roadmap.module.ts | 12 ++ src/roadmap/roadmap.service.ts | 118 +++++++++++++++++++ src/shared/configs/database.config.ts | 12 +- src/user/user.entity.ts | 6 +- 12 files changed, 459 insertions(+), 10 deletions(-) create mode 100644 src/roadmap/dtos/create-roadmap.dto.ts create mode 100644 src/roadmap/dtos/index.ts create mode 100644 src/roadmap/dtos/roadmap-response.dto.ts create mode 100644 src/roadmap/dtos/update-roadmap.dto.ts create mode 100644 src/roadmap/roadmap.controller.ts create mode 100644 src/roadmap/roadmap.entity.ts create mode 100644 src/roadmap/roadmap.module.ts create mode 100644 src/roadmap/roadmap.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index a10621f..cb6456d 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -15,10 +15,15 @@ import { CourseModuleModule } from './course-module/course-module.module'; import { CourseModule } from './course/course.module'; import { DatabaseModule } from './database/database.module'; import { EnrollmentModule } from './enrollment/enrollment.module'; +import { ExamAnswerModule } from './exam-answer/exam-answer.module'; +import { ExamAttemptModule } from './exam-attempt/exam-attempt.module'; import { ExamModule } from './exam/exam.module'; import { FileModule } from './file/file.module'; import { ProgressModule } from './progress/progress.module'; import { QuestionOptionModule } from './question-option/question-option.module'; +import { QuestionModule } from './question/question.module'; +import { RewardModule } from './reward/reward.module'; +import { RoadmapModule } from './roadmap/roadmap.module'; import { databaseConfig } from './shared/configs/database.config'; import { dotenvConfig } from './shared/configs/dotenv.config'; import { GLOBAL_CONFIG } from './shared/constants/global-config.constant'; @@ -28,11 +33,7 @@ import { UserBackgroundModule } from './user-background/user-background.module'; import { UserOccupationModule } from './user-occupation/user-occupation.module'; import { UserStreakModule } from './user-streak/user-streak.module'; import { UserModule } from './user/user.module'; -import { ExamAnswerModule } from './exam-answer/exam-answer.module'; -import { RewardModule } from './reward/reward.module'; import { UserRewardModule } from './userReward/user-reward.module'; -import { ExamAttemptModule } from './exam-attempt/exam-attempt.module'; -import { QuestionModule } from './question/question.module'; @Module({ imports: [ @@ -77,6 +78,7 @@ import { QuestionModule } from './question/question.module'; ChatMessageModule, RewardModule, UserRewardModule, + RoadmapModule, ], controllers: [AppController], providers: [ diff --git a/src/course/course.entity.ts b/src/course/course.entity.ts index 2b0d72a..240dbc5 100644 --- a/src/course/course.entity.ts +++ b/src/course/course.entity.ts @@ -1,6 +1,7 @@ import { Category } from 'src/category/category.entity'; import { CourseModule } from 'src/course-module/course-module.entity'; import { Enrollment } from 'src/enrollment/enrollment.entity'; +import { Roadmap } from 'src/roadmap/roadmap.entity'; import { CourseLevel } from 'src/shared/enums/course-level.enum'; import { CourseStatus } from 'src/shared/enums/course-status.enum'; import { User } from 'src/user/user.entity'; @@ -9,6 +10,8 @@ import { CreateDateColumn, Entity, JoinColumn, + JoinTable, + ManyToMany, ManyToOne, OneToMany, PrimaryGeneratedColumn, @@ -38,6 +41,9 @@ export class Course { @JoinColumn({ name: 'category_id' }) category: Category; + @ManyToMany(() => Roadmap, (roadmap) => roadmap.courses) + roadmaps: Roadmap[]; + @OneToMany(() => CourseModule, (courseModule) => courseModule.course, { cascade: true, }) diff --git a/src/roadmap/dtos/create-roadmap.dto.ts b/src/roadmap/dtos/create-roadmap.dto.ts new file mode 100644 index 0000000..3de89e4 --- /dev/null +++ b/src/roadmap/dtos/create-roadmap.dto.ts @@ -0,0 +1,31 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsNumber, IsString, IsUUID } from 'class-validator'; + +export class CreateRoadmapDto { + @IsString() + @IsNotEmpty() + @ApiProperty({ + description: 'Roadmap duration', + type: String, + example: '2 months', + }) + duration: string; + + @IsNumber() + @IsNotEmpty() + @ApiProperty({ + description: 'Roadmap priority', + type: Number, + example: 1, + }) + priority: number; + + @IsUUID('4', { each: true }) + @IsNotEmpty() + @ApiProperty({ + description: 'Roadmap courses', + type: [String], + example: ['8d4887aa-28e7-4d0e-844c-28a8ccead003'], + }) + courses: string[]; +} diff --git a/src/roadmap/dtos/index.ts b/src/roadmap/dtos/index.ts new file mode 100644 index 0000000..17157e8 --- /dev/null +++ b/src/roadmap/dtos/index.ts @@ -0,0 +1,3 @@ +export * from './create-roadmap.dto'; +export * from './roadmap-response.dto'; +export * from './update-roadmap.dto'; diff --git a/src/roadmap/dtos/roadmap-response.dto.ts b/src/roadmap/dtos/roadmap-response.dto.ts new file mode 100644 index 0000000..39486b2 --- /dev/null +++ b/src/roadmap/dtos/roadmap-response.dto.ts @@ -0,0 +1,77 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { CourseResponseDto } from 'src/course/dtos'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { UserResponseDto } from 'src/user/dtos/user-response.dto'; +import { Roadmap } from '../roadmap.entity'; + +export class RoadmapResponseDto { + @ApiProperty({ + description: 'Roadmap ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + id: string; + + @ApiProperty({ + description: 'Roadmap duration', + type: String, + example: '2 months', + }) + duration: string; + + @ApiProperty({ + description: 'Roadmap priority', + type: Number, + example: 1, + }) + priority: number; + + @ApiProperty({ + description: 'Roadmap user', + type: UserResponseDto, + }) + user: UserResponseDto; + + @ApiProperty({ + description: 'Roadmap courses', + type: [CourseResponseDto], + }) + courses: CourseResponseDto[]; + + @ApiProperty({ + description: 'Roadmap creation date', + type: Date, + }) + createdAt: Date; + + @ApiProperty({ + description: 'Roadmap update date', + type: Date, + }) + updatedAt: Date; + + constructor(roadmap: Roadmap) { + this.id = roadmap.id; + this.duration = roadmap.duration; + this.priority = roadmap.priority; + this.user = new UserResponseDto(roadmap.user); + this.courses = roadmap.courses.map( + (course) => new CourseResponseDto(course), + ); + this.createdAt = roadmap.createdAt; + this.updatedAt = roadmap.updatedAt; + } +} + +export class PaginatedRoadmapResponseDto extends PaginatedResponse( + RoadmapResponseDto, +) { + constructor( + roadmaps: RoadmapResponseDto[], + total: number, + pageSize: number, + currentPage: number, + ) { + super(roadmaps, total, pageSize, currentPage); + } +} diff --git a/src/roadmap/dtos/update-roadmap.dto.ts b/src/roadmap/dtos/update-roadmap.dto.ts new file mode 100644 index 0000000..e50a445 --- /dev/null +++ b/src/roadmap/dtos/update-roadmap.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateRoadmapDto } from './create-roadmap.dto'; + +export class UpdateRoadmapDto extends PartialType(CreateRoadmapDto) {} diff --git a/src/roadmap/roadmap.controller.ts b/src/roadmap/roadmap.controller.ts new file mode 100644 index 0000000..c9e3fd7 --- /dev/null +++ b/src/roadmap/roadmap.controller.ts @@ -0,0 +1,138 @@ +import { + Body, + Controller, + Delete, + Get, + HttpCode, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { CreateRoadmapDto, RoadmapResponseDto } from './dtos'; +import { RoadmapService } from './roadmap.service'; + +@Controller('roadmap') +@Injectable() +@ApiTags('Roadmap') +@ApiBearerAuth() +export class RoadmapController { + constructor(private readonly roadmapService: RoadmapService) {} + + @Get() + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all roadmaps', + type: RoadmapResponseDto, + isArray: true, + }) + @Roles(Role.ADMIN) + async findAll(@Query() query: PaginateQueryDto) { + return await this.roadmapService.findAll({ + ...query, + }); + } + + @Get('/user') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all roadmaps by user id', + type: RoadmapResponseDto, + isArray: true, + }) + @Roles(Role.STUDENT) + @Roles(Role.ADMIN) + async findAllByUser( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ) { + return await this.roadmapService.findAll({ + userId: request.user.id, + ...query, + }); + } + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns a roadmap by id', + type: RoadmapResponseDto, + }) + @Roles(Role.STUDENT) + @Roles(Role.ADMIN) + async findOne( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + }), + ) + id: string, + ) { + return await this.roadmapService.findOne({ where: { id } }); + } + + @Post() + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Creates a new roadmap', + type: RoadmapResponseDto, + }) + @Roles(Role.STUDENT) + @HttpCode(HttpStatus.CREATED) + async create( + @Req() request: AuthenticatedRequest, + @Body() createRoadmapDto: CreateRoadmapDto, + ) { + return await this.roadmapService.create(request.user.id, createRoadmapDto); + } + + @Patch(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Updates a roadmap by id', + type: RoadmapResponseDto, + }) + @Roles(Role.STUDENT) + @Roles(Role.ADMIN) + async update( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + }), + ) + id: string, + @Body() updateRoadmapDto: CreateRoadmapDto, + ) { + return await this.roadmapService.update({ id }, updateRoadmapDto); + } + + @Delete(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Deletes a roadmap by id', + type: RoadmapResponseDto, + }) + @Roles(Role.ADMIN) + @HttpCode(HttpStatus.NO_CONTENT) + async remove( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + }), + ) + id: string, + ) { + return await this.roadmapService.delete({ id }); + } +} diff --git a/src/roadmap/roadmap.entity.ts b/src/roadmap/roadmap.entity.ts new file mode 100644 index 0000000..3dd3264 --- /dev/null +++ b/src/roadmap/roadmap.entity.ts @@ -0,0 +1,52 @@ +import { Course } from 'src/course/course.entity'; +import { User } from 'src/user/user.entity'; +import { + Column, + CreateDateColumn, + Entity, + JoinTable, + ManyToMany, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +@Entity() +export class Roadmap { + @PrimaryGeneratedColumn('uuid') + id: string; + + @ManyToOne(() => User, { + nullable: false, + onDelete: 'CASCADE', + }) + user: User; + + @ManyToMany(() => Course, (course) => course.roadmaps) + @JoinTable() + courses: Course[]; + + @Column({ + nullable: false, + type: String, + }) + duration: string; + + @Column({ + nullable: false, + type: Number, + }) + priority: number; + + @CreateDateColumn({ + type: 'timestamp with time zone', + nullable: false, + }) + createdAt: Date; + + @UpdateDateColumn({ + type: 'timestamp with time zone', + nullable: false, + }) + updatedAt: Date; +} diff --git a/src/roadmap/roadmap.module.ts b/src/roadmap/roadmap.module.ts new file mode 100644 index 0000000..8ac6a0c --- /dev/null +++ b/src/roadmap/roadmap.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { RoadmapController } from './roadmap.controller'; +import { Roadmap } from './roadmap.entity'; +import { RoadmapService } from './roadmap.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Roadmap])], + controllers: [RoadmapController], + providers: [RoadmapService], +}) +export class RoadmapModule {} diff --git a/src/roadmap/roadmap.service.ts b/src/roadmap/roadmap.service.ts new file mode 100644 index 0000000..4cfc91d --- /dev/null +++ b/src/roadmap/roadmap.service.ts @@ -0,0 +1,118 @@ +import { + Inject, + Injectable, + InternalServerErrorException, + NotFoundException, +} from '@nestjs/common'; +import { createPagination } from 'src/shared/pagination'; +import { FindOneOptions, FindOptionsWhere, Repository } from 'typeorm'; +import { + CreateRoadmapDto, + PaginatedRoadmapResponseDto, + UpdateRoadmapDto, +} from './dtos'; +import { Roadmap } from './roadmap.entity'; + +@Injectable() +export class RoadmapService { + constructor( + @Inject('RoadmapRepository') + private readonly roadmapRepository: Repository, + ) {} + + async create( + userId: string, + createRoadmapDto: CreateRoadmapDto, + ): Promise { + try { + return await this.roadmapRepository.save({ + ...createRoadmapDto, + user: { id: userId }, + courses: createRoadmapDto.courses.map((courseId) => ({ id: courseId })), + }); + } catch (error) { + if (error instanceof NotFoundException) throw error; + throw new InternalServerErrorException(error.message); + } + } + + async findAll({ + userId, + page = 1, + limit = 20, + search = '', + }: { + userId?: string; + page?: number; + limit?: number; + search?: string; + }): Promise { + const { find } = await createPagination(this.roadmapRepository, { + page, + limit, + }); + + const queryBuilder = this.roadmapRepository + .createQueryBuilder('roadmap') + .leftJoinAndSelect('roadmap.user', 'user') + .leftJoinAndSelect('roadmap.courses', 'courses'); + + if (userId) { + queryBuilder.andWhere('user.id = :userId', { userId }); + } + + if (search) { + queryBuilder.andWhere( + '(roadmap.duration ILIKE :search OR courses.title ILIKE :search)', + { search: `%${search}%` }, + ); + } + + const [data, total] = await queryBuilder + .skip((page - 1) * limit) + .take(limit) + .getManyAndCount(); + + return new PaginatedRoadmapResponseDto(data, total, limit, page); + } + async findOne(options: FindOneOptions): Promise { + try { + const roadmap = await this.roadmapRepository.findOne(options); + if (!roadmap) throw new NotFoundException('Roadmap not found'); + return roadmap; + } catch (error) { + if (error instanceof NotFoundException) throw error; + throw new InternalServerErrorException(error.message); + } + } + + async update( + criteria: FindOptionsWhere, + updateRoadmapDto: UpdateRoadmapDto, + ): Promise { + try { + const roadmap = await this.findOne({ where: criteria }); + return await this.roadmapRepository.save({ + ...roadmap, + ...updateRoadmapDto, + courses: updateRoadmapDto.courses.map((courseId) => ({ id: courseId })), + }); + } catch (error) { + if (error instanceof NotFoundException) throw error; + throw new InternalServerErrorException(error.message); + } + } + + async delete(criteria: FindOptionsWhere): Promise { + try { + const roadmap = await this.findOne({ where: criteria }); + if (!roadmap) { + throw new NotFoundException('Roadmap not found'); + } + return await this.roadmapRepository.remove(roadmap); + } catch (error) { + if (error instanceof NotFoundException) throw error; + throw new InternalServerErrorException(error.message); + } + } +} diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index 7d3ae68..6ef4ea9 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -2,26 +2,27 @@ import { ConfigService } from '@nestjs/config'; import 'dotenv/config'; import { Category } from 'src/category/category.entity'; import { Chapter } from 'src/chapter/chapter.entity'; +import { ChatMessage } from 'src/chat-message/chat-message.entity'; +import { ChatRoom } from 'src/chat-room/chat-room.entity'; import { CourseModule } from 'src/course-module/course-module.entity'; import { Course } from 'src/course/course.entity'; import { Enrollment } from 'src/enrollment/enrollment.entity'; +import { ExamAnswer } from 'src/exam-answer/exam-answer.entity'; import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; import { Exam } from 'src/exam/exam.entity'; import { Progress } from 'src/progress/progress.entity'; import { QuestionOption } from 'src/question-option/question-option.entity'; import { Question } from 'src/question/question.entity'; +import { Reward } from 'src/reward/reward.entity'; +import { Roadmap } from 'src/roadmap/roadmap.entity'; import { UserBackgroundTopic } from 'src/user-background-topic/user-background-topic.entity'; import { UserBackground } from 'src/user-background/user-background.entity'; import { UserOccupation } from 'src/user-occupation/user-occupation.entity'; import { UserStreak } from 'src/user-streak/user-streak.entity'; import { User } from 'src/user/user.entity'; +import { UserReward } from 'src/userReward/user-reward.entity'; import { DataSource, DataSourceOptions } from 'typeorm'; import { GLOBAL_CONFIG } from '../constants/global-config.constant'; -import { ExamAnswer } from 'src/exam-answer/exam-answer.entity'; -import { Reward } from 'src/reward/reward.entity'; -import { UserReward } from 'src/userReward/user-reward.entity'; -import { ChatRoom } from 'src/chat-room/chat-room.entity'; -import { ChatMessage } from 'src/chat-message/chat-message.entity'; const configService = new ConfigService(); @@ -54,6 +55,7 @@ export const databaseConfig: DataSourceOptions = { UserBackground, ChatRoom, ChatMessage, + Roadmap, ], }; diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index 06670c4..a6fe352 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -1,8 +1,10 @@ import { Course } from 'src/course/course.entity'; import { Enrollment } from 'src/enrollment/enrollment.entity'; import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; +import { Roadmap } from 'src/roadmap/roadmap.entity'; import { Role } from 'src/shared/enums/roles.enum'; import { UserBackground } from 'src/user-background/user-background.entity'; +import { UserReward } from 'src/userReward/user-reward.entity'; import { Column, CreateDateColumn, @@ -11,7 +13,6 @@ import { PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; -import { UserReward } from 'src/userReward/user-reward.entity'; @Entity() export class User { @@ -40,6 +41,9 @@ export class User { @OneToMany(() => UserBackground, (background) => background.user) backgrounds: UserBackground[]; + @OneToMany(() => Roadmap, (roadmap) => roadmap.user) + roadmaps: Roadmap[]; + @Column({ nullable: false, unique: true, From 59493464503fc5461c91af85ae79ee7255f86df0 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Tue, 19 Nov 2024 13:24:05 +0700 Subject: [PATCH 084/155] feat: enhance chat room and message entities with improved relationships and structure --- src/chapter/chapter.controller.ts | 292 ++++++----- src/chapter/chapter.module.ts | 2 + src/chapter/chapter.service.ts | 485 ++++++++++-------- src/chat-message/chat-message.entity.ts | 81 +-- src/chat-room/chat-room.controller.ts | 278 +++++----- src/chat-room/chat-room.entity.ts | 114 ++-- src/chat-room/chat-room.service.ts | 148 +++--- .../dtos/paginated-chat-room-response.dto.ts | 111 ++-- .../guards/chat-room-ownership.guard.ts | 74 ++- src/course/course.service.ts | 340 ++++++------ 10 files changed, 1016 insertions(+), 909 deletions(-) diff --git a/src/chapter/chapter.controller.ts b/src/chapter/chapter.controller.ts index 611823b..536cec5 100644 --- a/src/chapter/chapter.controller.ts +++ b/src/chapter/chapter.controller.ts @@ -1,25 +1,25 @@ import { - BadRequestException, - Body, - Controller, - Delete, - Get, - HttpStatus, - Injectable, - Param, - ParseUUIDPipe, - Patch, - Post, - Query, - Req, - UseGuards, + BadRequestException, + Body, + Controller, + Delete, + Get, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, + UseGuards, } from '@nestjs/common'; import { - ApiBearerAuth, - ApiParam, - ApiQuery, - ApiResponse, - ApiTags, + ApiBearerAuth, + ApiParam, + ApiQuery, + ApiResponse, + ApiTags, } from '@nestjs/swagger'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { Roles } from 'src/shared/decorators/role.decorator'; @@ -27,138 +27,160 @@ import { Role } from 'src/shared/enums'; import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { ChapterService } from './chapter.service'; import { - ChapterResponseDto, - PaginatedChapterResponseDto, + ChapterResponseDto, + PaginatedChapterResponseDto, } from './dtos/chapter-response.dto'; import { CreateChapterDto } from './dtos/create-chapter.dto'; import { UpdateChapterDto } from './dtos/update-chapter.dto'; import { CourseOwnership } from 'src/shared/decorators/course-ownership.decorator'; import { CourseModuleService } from 'src/course-module/course-module.service'; -import { Course } from 'src/course/course.entity'; +import { ChatRoomResponseDto } from 'src/chat-room/dtos'; +import { ChatRoomService } from 'src/chat-room/chat-room.service'; @Controller('chapter') @ApiTags('Chapters') @ApiBearerAuth() @Injectable() export class ChapterController { - constructor( - private readonly chapterService: ChapterService, - private readonly courseModuleService: CourseModuleService, - ) {} + constructor( + private readonly chapterService: ChapterService, + private readonly courseModuleService: CourseModuleService, + ) { } - @Get() - @ApiResponse({ - status: HttpStatus.OK, - type: ChapterResponseDto, - description: 'Get all chapters', - isArray: true, - }) - @ApiQuery({ - name: 'page', - type: Number, - required: false, - description: 'Page number', - }) - @ApiQuery({ - name: 'limit', - type: Number, - required: false, - description: 'Items per page', - }) - async findAll( - @Req() request: AuthenticatedRequest, - @Query() query: PaginateQueryDto, - ): Promise { - return this.chapterService.findAll({ - page: query.page, - limit: query.limit, - search: query.search, - userId: request.user.id, - role: request.user.role, - }); - } + @Get() + @ApiResponse({ + status: HttpStatus.OK, + type: ChapterResponseDto, + description: 'Get all chapters', + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + async findAll( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return this.chapterService.findAll({ + page: query.page, + limit: query.limit, + search: query.search, + userId: request.user.id, + role: request.user.role, + }); + } + + @Get(':id/chat-rooms') + @ApiResponse({ + status: HttpStatus.OK, + type: ChatRoomResponseDto, + description: 'Get all chat rooms for a chapter', + isArray: true, + }) + @ApiParam({ + name: 'id', + type: String, + description: 'Chapter ID', + }) + @Roles(Role.STUDENT) + async getChatRooms( + @Req() request: AuthenticatedRequest, + @Param('id', ParseUUIDPipe) id: string, + ): Promise { + const chatRooms = await this.chapterService.getChatRooms(request.user.id, id); + return chatRooms.map((chatRoom) => new ChatRoomResponseDto(chatRoom)); + } - @Get(':id') - @ApiResponse({ - status: HttpStatus.OK, - type: ChapterResponseDto, - description: 'Get a chapter by ID', - }) - @ApiParam({ - name: 'id', - type: String, - description: 'Chapter ID', - }) - async findOne( - @Req() request: AuthenticatedRequest, - @Param('id', ParseUUIDPipe) id: string, - ): Promise { - return this.chapterService.findOne(request.user.id, request.user.role, { - where: { id }, - }); - } + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + type: ChapterResponseDto, + description: 'Get a chapter by ID', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'Chapter ID', + }) + async findOne( + @Req() request: AuthenticatedRequest, + @Param('id', ParseUUIDPipe) id: string, + ): Promise { + return this.chapterService.findOne(request.user.id, request.user.role, { + where: { id }, + }); + } - @Post() - @Roles(Role.TEACHER) - @ApiResponse({ - status: HttpStatus.CREATED, - type: ChapterResponseDto, - description: 'Create a chapter', - }) - async create( - @Req() request: AuthenticatedRequest, - @Body() createChapterDto: CreateChapterDto, - ): Promise { - if (createChapterDto.moduleId != null) { - await this.courseModuleService.validateOwnership( - createChapterDto.moduleId, - request.user.id, - ); + @Post() + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.CREATED, + type: ChapterResponseDto, + description: 'Create a chapter', + }) + async create( + @Req() request: AuthenticatedRequest, + @Body() createChapterDto: CreateChapterDto, + ): Promise { + if (createChapterDto.moduleId != null) { + await this.courseModuleService.validateOwnership( + createChapterDto.moduleId, + request.user.id, + ); + } + return this.chapterService.create(createChapterDto); } - return this.chapterService.create(createChapterDto); - } - @Patch(':id') - @CourseOwnership({ adminDraftOnly: true }) - @ApiResponse({ - status: HttpStatus.OK, - type: ChapterResponseDto, - description: 'Update a chapter', - }) - @ApiParam({ - name: 'id', - type: String, - description: 'Chapter ID', - }) - async update( - @Req() request: AuthenticatedRequest, - @Param('id', ParseUUIDPipe) id: string, - @Body() updateChapterDto: UpdateChapterDto, - ): Promise { - if (updateChapterDto.moduleId != null) { - await this.courseModuleService.validateOwnership( - updateChapterDto.moduleId, - request.user.id, - ); + @Patch(':id') + @CourseOwnership({ adminDraftOnly: true }) + @ApiResponse({ + status: HttpStatus.OK, + type: ChapterResponseDto, + description: 'Update a chapter', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'Chapter ID', + }) + async update( + @Req() request: AuthenticatedRequest, + @Param('id', ParseUUIDPipe) id: string, + @Body() updateChapterDto: UpdateChapterDto, + ): Promise { + if (updateChapterDto.moduleId != null) { + await this.courseModuleService.validateOwnership( + updateChapterDto.moduleId, + request.user.id, + ); + } + return this.chapterService.update(id, updateChapterDto); } - return this.chapterService.update(id, updateChapterDto); - } - @Delete(':id') - @CourseOwnership() - @ApiResponse({ - status: HttpStatus.OK, - type: ChapterResponseDto, - description: 'Delete a chapter', - }) - @ApiParam({ - name: 'id', - type: String, - description: 'Chapter ID', - }) - async remove( - @Param('id', ParseUUIDPipe) id: string, - ): Promise { - return this.chapterService.remove(id); - } + @Delete(':id') + @CourseOwnership() + @ApiResponse({ + status: HttpStatus.OK, + type: ChapterResponseDto, + description: 'Delete a chapter', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'Chapter ID', + }) + async remove( + @Param('id', ParseUUIDPipe) id: string, + ): Promise { + return this.chapterService.remove(id); + } } diff --git a/src/chapter/chapter.module.ts b/src/chapter/chapter.module.ts index f53abbb..e990b62 100644 --- a/src/chapter/chapter.module.ts +++ b/src/chapter/chapter.module.ts @@ -8,6 +8,7 @@ import { CourseModuleModule } from 'src/course-module/course-module.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { CourseModule } from 'src/course-module/course-module.entity'; import { ChatRoomModule } from 'src/chat-room/chat-room.module'; +import { EnrollmentModule } from 'src/enrollment/enrollment.module'; @Module({ imports: [ @@ -15,6 +16,7 @@ import { ChatRoomModule } from 'src/chat-room/chat-room.module'; CourseModuleModule, TypeOrmModule.forFeature([Chapter, CourseModule]), ChatRoomModule, + EnrollmentModule, ], controllers: [ChapterController], providers: [...chapterProviders, ChapterService], diff --git a/src/chapter/chapter.service.ts b/src/chapter/chapter.service.ts index 30e494e..e670b3b 100644 --- a/src/chapter/chapter.service.ts +++ b/src/chapter/chapter.service.ts @@ -1,7 +1,8 @@ import { - BadRequestException, - Injectable, - NotFoundException, + BadRequestException, + ForbiddenException, + Injectable, + NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { createPagination } from 'src/shared/pagination'; @@ -13,256 +14,284 @@ import { UpdateChapterDto } from './dtos/update-chapter.dto'; import { CourseStatus, Role } from 'src/shared/enums'; import { ChatRoomService } from 'src/chat-room/chat-room.service'; import { ChatRoomStatus, ChatRoomType } from 'src/chat-room/enums'; +import { EnrollmentService } from 'src/enrollment/enrollment.service'; +import { ChatRoom } from 'src/chat-room/chat-room.entity'; @Injectable() export class ChapterService { - constructor( - @InjectRepository(Chapter) - private readonly chapterRepository: Repository, - private readonly chatRoomService: ChatRoomService, - ) {} - - async findAll({ - page = 1, - limit = 20, - search = '', - userId, - role, - }: { - page?: number; - limit?: number; - search?: string; - userId: string; - role: Role; - }): Promise { - const { find } = await createPagination(this.chapterRepository, { - page, - limit, - }); - - const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; - const whereCondition = this.buildWhereCondition(userId, role, baseSearch); - - const chapters = await find({ - where: whereCondition, - relations: { - module: true, - }, - }).run(); - - return chapters; - } - - async findOne( - userId: string, - role: Role, - options: FindOneOptions, - ): Promise { - const baseWhere = options.where as FindOptionsWhere; - const whereCondition = this.buildWhereCondition(userId, role, baseWhere); - - const chapter = await this.chapterRepository.findOne({ - where: whereCondition, - relations: { - module: true, - }, - }); - - if (!chapter) { - throw new NotFoundException('Chapter not found'); - } - - return chapter; - } + constructor( + @InjectRepository(Chapter) + private readonly chapterRepository: Repository, + private readonly chatRoomService: ChatRoomService, + private readonly enrollmentService: EnrollmentService, + ) { } + + async findAll({ + page = 1, + limit = 20, + search = '', + userId, + role, + }: { + page?: number; + limit?: number; + search?: string; + userId: string; + role: Role; + }): Promise { + const { find } = await createPagination(this.chapterRepository, { + page, + limit, + }); - async validateAndGetNextOrderIndex(moduleId: string): Promise { - const existingChapter = await this.chapterRepository.find({ - where: { moduleId }, - order: { orderIndex: 'DESC' }, - }); + const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; + const whereCondition = this.buildWhereCondition(userId, role, baseSearch); - const nextOrderIndex = existingChapter.map((chapter) => chapter.orderIndex); - const hasDuplicates = - new Set(nextOrderIndex).size !== nextOrderIndex.length; + const chapters = await find({ + where: whereCondition, + relations: { + module: true, + }, + }).run(); - if (hasDuplicates) { - throw new BadRequestException('Order index is duplicated'); + return chapters; } - return nextOrderIndex.length ? nextOrderIndex[0] + 1 : 1; - } - - async create(createChapterDto: CreateChapterDto): Promise { - let orderIndex = await this.validateAndGetNextOrderIndex( - createChapterDto.moduleId, - ); - - const createdChapter = this.chapterRepository.create({ - ...createChapterDto, - orderIndex: orderIndex, - }); - const savedChapter = await this.chapterRepository.save(createdChapter); - await this.chatRoomService.create({ - title: `${savedChapter.title} Questions`, - type: ChatRoomType.QUESTION, - chapterId: savedChapter.id, - status: ChatRoomStatus.ACTIVE, - }); - await this.chatRoomService.create({ - title: `${savedChapter.title} Discussion`, - type: ChatRoomType.DISCUSSION, - chapterId: savedChapter.id, - status: ChatRoomStatus.ACTIVE, - }); - return savedChapter; - } - - async reorderModules(moduleId: string): Promise { - const modulesToReorder = await this.chapterRepository.find({ - where: { moduleId }, - order: { orderIndex: 'ASC' }, - }); - - for (let i = 0; i < modulesToReorder.length; i++) { - modulesToReorder[i].orderIndex = i + 1; + async findOne( + userId: string, + role: Role, + options: FindOneOptions, + ): Promise { + const baseWhere = options.where as FindOptionsWhere; + const whereCondition = this.buildWhereCondition(userId, role, baseWhere); + + const chapter = await this.chapterRepository.findOne({ + where: whereCondition, + relations: { + module: true, + }, + }); + + if (!chapter) { + throw new NotFoundException('Chapter not found'); + } + + return chapter; } - await this.chapterRepository.save(modulesToReorder); - } + async validateAndGetNextOrderIndex(moduleId: string): Promise { + const existingChapter = await this.chapterRepository.find({ + where: { moduleId }, + order: { orderIndex: 'DESC' }, + }); - async update( - id: string, - updateChapterDto: UpdateChapterDto, - ): Promise { - const chapter = await this.chapterRepository.findOne({ where: { id } }); + const nextOrderIndex = existingChapter.map((chapter) => chapter.orderIndex); + const hasDuplicates = + new Set(nextOrderIndex).size !== nextOrderIndex.length; - if (!chapter) { - throw new NotFoundException('Chapter not found'); - } - if (updateChapterDto.orderIndex != null) { - await this.validateOrderIndex( - chapter.moduleId, - updateChapterDto.orderIndex, - ); + if (hasDuplicates) { + throw new BadRequestException('Order index is duplicated'); + } + + return nextOrderIndex.length ? nextOrderIndex[0] + 1 : 1; } - if ( - updateChapterDto.orderIndex && - updateChapterDto.orderIndex !== chapter.orderIndex - ) { - const existingChapter = await this.chapterRepository.findOne({ - where: { - moduleId: chapter.moduleId, - orderIndex: updateChapterDto.orderIndex, - }, - }); - - if (existingChapter) { - await this.chapterRepository.update(existingChapter.id, { - orderIndex: chapter.orderIndex, + + async create(createChapterDto: CreateChapterDto): Promise { + let orderIndex = await this.validateAndGetNextOrderIndex( + createChapterDto.moduleId, + ); + + const createdChapter = this.chapterRepository.create({ + ...createChapterDto, + orderIndex: orderIndex, + }); + const savedChapter = await this.chapterRepository.save(createdChapter); + await this.chatRoomService.create({ + title: `${savedChapter.title} Questions`, + type: ChatRoomType.QUESTION, + chapterId: savedChapter.id, + status: ChatRoomStatus.ACTIVE, }); - } + await this.chatRoomService.create({ + title: `${savedChapter.title} Discussion`, + type: ChatRoomType.DISCUSSION, + chapterId: savedChapter.id, + status: ChatRoomStatus.ACTIVE, + }); + return savedChapter; } - Object.assign(chapter, updateChapterDto); - await this.chapterRepository.save(chapter); - - return chapter; - } + async reorderModules(moduleId: string): Promise { + const modulesToReorder = await this.chapterRepository.find({ + where: { moduleId }, + order: { orderIndex: 'ASC' }, + }); - async remove(id: string): Promise { - const chapter = await this.chapterRepository.findOne({ where: { id } }); + for (let i = 0; i < modulesToReorder.length; i++) { + modulesToReorder[i].orderIndex = i + 1; + } - if (!chapter) { - throw new BadRequestException('Chapter not found'); + await this.chapterRepository.save(modulesToReorder); } - const result = await this.chapterRepository.remove(chapter); - - await this.reorderModules(chapter.moduleId); - - return result; - } - - private async validateOrderIndex( - moduleId: string, - orderIndex: number, - ): Promise { - const existingModules = await this.chapterRepository.find({ - where: { moduleId }, - order: { orderIndex: 'ASC' }, - }); - if (existingModules.length === 0) { - if (orderIndex !== 1) { - throw new BadRequestException( - 'Order index should be 1 when there are no modules in the course', - ); - } - return; + async update( + id: string, + updateChapterDto: UpdateChapterDto, + ): Promise { + const chapter = await this.chapterRepository.findOne({ where: { id } }); + + if (!chapter) { + throw new NotFoundException('Chapter not found'); + } + if (updateChapterDto.orderIndex != null) { + await this.validateOrderIndex( + chapter.moduleId, + updateChapterDto.orderIndex, + ); + } + if ( + updateChapterDto.orderIndex && + updateChapterDto.orderIndex !== chapter.orderIndex + ) { + const existingChapter = await this.chapterRepository.findOne({ + where: { + moduleId: chapter.moduleId, + orderIndex: updateChapterDto.orderIndex, + }, + }); + + if (existingChapter) { + await this.chapterRepository.update(existingChapter.id, { + orderIndex: chapter.orderIndex, + }); + } + } + + Object.assign(chapter, updateChapterDto); + await this.chapterRepository.save(chapter); + + return chapter; } - const minIndex = 1; - const maxIndex = existingModules[existingModules.length - 1].orderIndex; - if (orderIndex < minIndex || orderIndex > maxIndex) { - throw new BadRequestException( - `Order index must be between ${minIndex} and ${maxIndex}`, - ); + async remove(id: string): Promise { + const chapter = await this.chapterRepository.findOne({ where: { id } }); + + if (!chapter) { + throw new BadRequestException('Chapter not found'); + } + + const result = await this.chapterRepository.remove(chapter); + + await this.reorderModules(chapter.moduleId); + + return result; } - } - async validateOwnership(id: string, userId: string): Promise { - const chapter = await this.chapterRepository.findOne({ - where: { id }, - relations: { module: { course: { teacher: true } } }, - }); - if (!chapter) throw new NotFoundException('Chapter not found'); - if (chapter.module.course.teacher.id !== userId) - throw new BadRequestException('You can only access your own courses'); - } - private buildWhereCondition( - userId: string, - role: Role, - baseCondition: FindOptionsWhere = {}, - ) { - const conditions: Record< - Role, - () => FindOptionsWhere | FindOptionsWhere[] - > = { - [Role.STUDENT]: () => ({ - ...baseCondition, - module: { - course: { - status: CourseStatus.PUBLISHED, - }, - }, - }), - [Role.TEACHER]: () => [ - { - ...baseCondition, - module: { - course: { - status: CourseStatus.PUBLISHED, - }, - }, - }, - { - ...baseCondition, - module: { - course: { - teacher: { - id: userId, - }, - }, - }, - }, - ], - [Role.ADMIN]: () => baseCondition, - }; - const buildCondition = conditions[role]; + private async validateOrderIndex( + moduleId: string, + orderIndex: number, + ): Promise { + const existingModules = await this.chapterRepository.find({ + where: { moduleId }, + order: { orderIndex: 'ASC' }, + }); + if (existingModules.length === 0) { + if (orderIndex !== 1) { + throw new BadRequestException( + 'Order index should be 1 when there are no modules in the course', + ); + } + return; + } + const minIndex = 1; + const maxIndex = existingModules[existingModules.length - 1].orderIndex; + + if (orderIndex < minIndex || orderIndex > maxIndex) { + throw new BadRequestException( + `Order index must be between ${minIndex} and ${maxIndex}`, + ); + } + } + async validateOwnership(id: string, userId: string): Promise { + const chapter = await this.chapterRepository.findOne({ + where: { id }, + relations: { module: { course: { teacher: true } } }, + }); + if (!chapter) throw new NotFoundException('Chapter not found'); + if (chapter.module.course.teacher.id !== userId) + throw new BadRequestException('You can only access your own courses'); + } - if (!buildCondition) { - throw new BadRequestException('Invalid role'); + async getChatRooms(userId: string, chapterId: string): Promise { + try { + const chapter = await this.chapterRepository.findOne({ + where: { id: chapterId }, + relations: { module: { course: true } }, + }); + const courseId = chapter.module.course.id; + const enrollments = await this.enrollmentService.findOne({ + user: { id: userId }, + course: { id: courseId }, + }) + if (!enrollments) + throw new ForbiddenException('You are not enrolled in this course'); + return await this.chatRoomService.find({ + where: { + chapter: { id: chapterId }, + status: ChatRoomStatus.ACTIVE, + } + }) + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); + } } - return buildCondition(); - } + private buildWhereCondition( + userId: string, + role: Role, + baseCondition: FindOptionsWhere = {}, + ) { + const conditions: Record< + Role, + () => FindOptionsWhere | FindOptionsWhere[] + > = { + [Role.STUDENT]: () => ({ + ...baseCondition, + module: { + course: { + status: CourseStatus.PUBLISHED, + }, + }, + }), + [Role.TEACHER]: () => [ + { + ...baseCondition, + module: { + course: { + status: CourseStatus.PUBLISHED, + }, + }, + }, + { + ...baseCondition, + module: { + course: { + teacher: { + id: userId, + }, + }, + }, + }, + ], + [Role.ADMIN]: () => baseCondition, + }; + + const buildCondition = conditions[role]; + + if (!buildCondition) { + throw new BadRequestException('Invalid role'); + } + + return buildCondition(); + } } diff --git a/src/chat-message/chat-message.entity.ts b/src/chat-message/chat-message.entity.ts index 1ce612d..95efca6 100644 --- a/src/chat-message/chat-message.entity.ts +++ b/src/chat-message/chat-message.entity.ts @@ -1,10 +1,10 @@ import { - Entity, - PrimaryGeneratedColumn, - Column, - OneToOne, - ManyToOne, - JoinColumn, + Entity, + PrimaryGeneratedColumn, + Column, + OneToOne, + ManyToOne, + JoinColumn, } from 'typeorm'; import { ChatMessageType } from './enums/chat-message-type.enum'; import { ChatRoom } from 'src/chat-room/chat-room.entity'; @@ -12,44 +12,45 @@ import { User } from 'src/user/user.entity'; @Entity() export class ChatMessage { - @PrimaryGeneratedColumn('uuid') - id: string; + @PrimaryGeneratedColumn('uuid') + id: string; - @Column({ - nullable: false, - }) - content: string; + @Column({ + nullable: false, + }) + content: string; - @OneToOne(() => ChatMessage, { - nullable: true, - onDelete: 'CASCADE', - }) - @JoinColumn({ name: 'reply' }) - reply?: ChatMessage; + @OneToOne(() => ChatMessage, { + nullable: true, + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'reply' }) + reply?: ChatMessage; - @Column({ - nullable: false, - default: false, - }) - isEdited: boolean; + @Column({ + nullable: false, + default: false, + }) + isEdited: boolean; - @Column({ - type: 'enum', - enum: ChatMessageType, - nullable: false, - }) - type: ChatMessageType; + @Column({ + type: 'enum', + enum: ChatMessageType, + nullable: false, + }) + type: ChatMessageType; - @ManyToOne(() => ChatRoom, { - nullable: false, - onDelete: 'CASCADE', - }) - chatRoom: ChatRoom; + @ManyToOne(() => ChatRoom, (chatRoom) => chatRoom.chatMessages, { + nullable: false, + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'chat_room_id' }) + chatRoom: ChatRoom; - @ManyToOne(() => User, { - nullable: false, - onDelete: 'CASCADE', - eager: true, - }) - user: User; + @ManyToOne(() => User, { + nullable: false, + onDelete: 'CASCADE', + eager: true, + }) + user: User; } diff --git a/src/chat-room/chat-room.controller.ts b/src/chat-room/chat-room.controller.ts index c9236dc..b99624f 100644 --- a/src/chat-room/chat-room.controller.ts +++ b/src/chat-room/chat-room.controller.ts @@ -1,163 +1,167 @@ import { - Controller, - Injectable, - Query, - HttpStatus, - Param, - ParseUUIDPipe, - Post, - HttpCode, - Body, - Patch, - Delete, - UseGuards, + Controller, + Injectable, + Query, + HttpStatus, + Param, + ParseUUIDPipe, + Post, + HttpCode, + Body, + Patch, + Delete, + UseGuards, + Req, } from '@nestjs/common'; import { Get } from '@nestjs/common'; import { ChatRoomService } from './chat-room.service'; import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { - ApiTags, - ApiBearerAuth, - ApiResponse, - ApiQuery, - ApiParam, + ApiTags, + ApiBearerAuth, + ApiResponse, + ApiQuery, + ApiParam, } from '@nestjs/swagger'; import { - ChatRoomResponseDto, - PaginatedChatRoomResponseDto, + ChatRoomResponseDto, + PaginatedChatRoomResponseDto, } from './dtos/paginated-chat-room-response.dto'; import { Roles } from 'src/shared/decorators/role.decorator'; import { Role } from 'src/shared/enums'; import { CreateChatRoomDto } from './dtos/create-chat-room.dto'; import { UpdateChatRoomDto } from './dtos/update-chat-room.dto'; import { ChatRoomOwnershipGuard } from './guards/chat-room-ownership.guard'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; @Controller('chat-room') @Injectable() @ApiTags('Chat Room') @ApiBearerAuth() export class ChatRoomController { - constructor(private readonly chatRoomService: ChatRoomService) {} + constructor(private readonly chatRoomService: ChatRoomService) { } - @Get() - @ApiResponse({ - status: HttpStatus.OK, - description: 'Get all chat rooms', - type: PaginatedChatRoomResponseDto, - }) - @ApiQuery({ - name: 'page', - type: Number, - required: false, - }) - @ApiQuery({ - name: 'limit', - type: Number, - required: false, - }) - @ApiQuery({ - name: 'search', - type: String, - required: false, - }) - @Roles(Role.ADMIN) - async findAll(@Query() query: PaginateQueryDto) { - return await this.chatRoomService.findAll(query); - } + @Get() + @ApiResponse({ + status: HttpStatus.OK, + description: 'Get all chat rooms', + type: PaginatedChatRoomResponseDto, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + }) + @Roles(Role.TEACHER) + async findAll( + @Query() query: PaginateQueryDto, + @Req() request: AuthenticatedRequest, + ) { + return await this.chatRoomService.findAll({ userId: request.user.id, ...query }); + } - @Get(':id') - @ApiResponse({ - status: HttpStatus.OK, - description: 'Get chat room by id', - type: ChatRoomResponseDto, - }) - @ApiParam({ - name: 'id', - type: String, - required: true, - }) - @Roles(Role.ADMIN, Role.STUDENT) - @UseGuards(ChatRoomOwnershipGuard) - async findById( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - ) { - return new ChatRoomResponseDto( - await this.chatRoomService.findOne({ where: { id } }), - ); - } + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Get chat room by id', + type: ChatRoomResponseDto, + }) + @ApiParam({ + name: 'id', + type: String, + required: true, + }) + @Roles(Role.TEACHER, Role.STUDENT) + @UseGuards(ChatRoomOwnershipGuard) + async findById( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ) { + return new ChatRoomResponseDto( + await this.chatRoomService.findOne({ where: { id } }), + ); + } - @Post() - @ApiResponse({ - status: HttpStatus.CREATED, - description: 'Create chat room', - type: ChatRoomResponseDto, - }) - @Roles(Role.ADMIN) - @HttpCode(HttpStatus.CREATED) - async create(@Body() createChatRoomDto: CreateChatRoomDto) { - return new ChatRoomResponseDto( - await this.chatRoomService.create(createChatRoomDto), - ); - } + @Post() + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Create chat room', + type: ChatRoomResponseDto, + }) + @Roles(Role.ADMIN) + @HttpCode(HttpStatus.CREATED) + async create(@Body() createChatRoomDto: CreateChatRoomDto) { + return new ChatRoomResponseDto( + await this.chatRoomService.create(createChatRoomDto), + ); + } - @Patch(':id') - @ApiResponse({ - status: HttpStatus.OK, - description: 'Update chat room', - type: ChatRoomResponseDto, - }) - @ApiParam({ - name: 'id', - type: String, - required: true, - }) - @Roles(Role.ADMIN) - async update( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - @Body() updateChatRoomDto: UpdateChatRoomDto, - ) { - return new ChatRoomResponseDto( - await this.chatRoomService.update({ id }, updateChatRoomDto), - ); - } + @Patch(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Update chat room', + type: ChatRoomResponseDto, + }) + @ApiParam({ + name: 'id', + type: String, + required: true, + }) + @Roles(Role.ADMIN) + async update( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @Body() updateChatRoomDto: UpdateChatRoomDto, + ) { + return new ChatRoomResponseDto( + await this.chatRoomService.update({ id }, updateChatRoomDto), + ); + } - @Delete(':id') - @ApiResponse({ - status: HttpStatus.NO_CONTENT, - description: 'Delete chat room', - type: ChatRoomResponseDto, - }) - @ApiParam({ - name: 'id', - type: String, - required: true, - }) - @Roles(Role.ADMIN) - @HttpCode(HttpStatus.NO_CONTENT) - async delete( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - ) { - return await this.chatRoomService.delete({ id }); - } + @Delete(':id') + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Delete chat room', + }) + @ApiParam({ + name: 'id', + type: String, + required: true, + }) + @Roles(Role.ADMIN) + @HttpCode(HttpStatus.NO_CONTENT) + async delete( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ) { + await this.chatRoomService.delete({ id }); + } } diff --git a/src/chat-room/chat-room.entity.ts b/src/chat-room/chat-room.entity.ts index 5056230..d05595c 100644 --- a/src/chat-room/chat-room.entity.ts +++ b/src/chat-room/chat-room.entity.ts @@ -1,63 +1,67 @@ import { - Entity, - PrimaryGeneratedColumn, - Column, - CreateDateColumn, - UpdateDateColumn, - OneToOne, - JoinColumn, - ManyToOne, + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + JoinColumn, + ManyToOne, + OneToMany, } from 'typeorm'; import { Chapter } from 'src/chapter/chapter.entity'; import { ChatRoomType, ChatRoomStatus } from './enums'; +import { ChatMessage } from 'src/chat-message/chat-message.entity'; @Entity() export class ChatRoom { - @PrimaryGeneratedColumn('uuid') - id: string; - - @ManyToOne(() => Chapter, (chapter) => chapter.chatRooms, { - onDelete: 'CASCADE', - nullable: false, - eager: true, - }) - @JoinColumn({ name: 'chapter_id' }) - chapter: Chapter; - - @Column({ - nullable: false, - }) - title: string; - - @Column({ - nullable: false, - type: 'enum', - enum: ChatRoomType, - default: ChatRoomType.QUESTION, - }) - type: ChatRoomType; - - @Column({ - nullable: false, - type: 'enum', - enum: ChatRoomStatus, - default: ChatRoomStatus.ACTIVE, - }) - status: ChatRoomStatus; - - @CreateDateColumn({ - type: 'timestamp', - }) - createdAt: Date; - - @Column({ - default: 0, - type: 'int', - }) - participantCount: number; - - @UpdateDateColumn({ - type: 'timestamp', - }) - updatedAt: Date; + @PrimaryGeneratedColumn('uuid') + id: string; + + @ManyToOne(() => Chapter, (chapter) => chapter.chatRooms, { + onDelete: 'CASCADE', + nullable: false, + eager: true, + }) + @JoinColumn({ name: 'chapter_id' }) + chapter: Chapter; + + @Column({ + nullable: false, + }) + title: string; + + @Column({ + nullable: false, + type: 'enum', + enum: ChatRoomType, + default: ChatRoomType.QUESTION, + }) + type: ChatRoomType; + + @Column({ + nullable: false, + type: 'enum', + enum: ChatRoomStatus, + default: ChatRoomStatus.ACTIVE, + }) + status: ChatRoomStatus; + + @CreateDateColumn({ + type: 'timestamp', + }) + createdAt: Date; + + @Column({ + default: 0, + type: 'int', + }) + participantCount: number; + + @UpdateDateColumn({ + type: 'timestamp', + }) + updatedAt: Date; + + @OneToMany(() => ChatMessage, (chatMessage) => chatMessage.chatRoom) + chatMessages: ChatMessage[]; } diff --git a/src/chat-room/chat-room.service.ts b/src/chat-room/chat-room.service.ts index bc4f4d5..dda0bec 100644 --- a/src/chat-room/chat-room.service.ts +++ b/src/chat-room/chat-room.service.ts @@ -1,86 +1,100 @@ import { - Injectable, - Inject, - InternalServerErrorException, - NotFoundException, + Injectable, + Inject, + InternalServerErrorException, + NotFoundException, } from '@nestjs/common'; -import { Repository, FindOneOptions, FindOptionsWhere } from 'typeorm'; +import { Repository, FindOneOptions, FindOptionsWhere, ILike, FindManyOptions } from 'typeorm'; import { ChatRoom } from './chat-room.entity'; import { createPagination } from 'src/shared/pagination'; import { - UpdateChatRoomDto, - PaginatedChatRoomResponseDto, - CreateChatRoomDto, + UpdateChatRoomDto, + PaginatedChatRoomResponseDto, + CreateChatRoomDto, } from './dtos'; +import { Role } from 'src/shared/enums'; @Injectable() export class ChatRoomService { - constructor( - @Inject('ChatRoomRepository') - private readonly chatRoomRepository: Repository, - ) {} + constructor( + @Inject('ChatRoomRepository') + private readonly chatRoomRepository: Repository, + ) { } - async create(createChatRoomDto: CreateChatRoomDto): Promise { - try { - return await this.chatRoomRepository.save({ - ...createChatRoomDto, - chapter: { id: createChatRoomDto.chapterId }, - }); - } catch (error) { - if (error instanceof Error) - throw new InternalServerErrorException(error.message); + async create(createChatRoomDto: CreateChatRoomDto): Promise { + try { + return await this.chatRoomRepository.save({ + ...createChatRoomDto, + chapter: { id: createChatRoomDto.chapterId }, + }); + } catch (error) { + if (error instanceof Error) + throw new InternalServerErrorException(error.message); + } } - } - async findAll({ - page = 1, - limit = 20, - search = '', - }: { - page?: number; - limit?: number; - search?: string; - }): Promise { - const { find } = await createPagination(this.chatRoomRepository, { - page, - limit, - }); - const chatRooms = await find({ - where: { chapter: { id: search } }, - }).run(); - return new PaginatedChatRoomResponseDto( - chatRooms.data, - chatRooms.meta.total, - chatRooms.meta.pageSize, - chatRooms.meta.currentPage, - ); - } + async find(criteria: FindManyOptions): Promise { + try { + return await this.chatRoomRepository.find(criteria); + } catch (error) { + if (error instanceof Error) throw new NotFoundException("Chat room not found"); + } + } + + async findAll({ + userId, + page = 1, + limit = 20, + search = '', + }: { + userId: string; + page?: number; + limit?: number; + search?: string; + }): Promise { + const { find } = await createPagination(this.chatRoomRepository, { + page, + limit, + }); + const chatRooms = await find({ + where: { + title: ILike(`%${search}%`), + chapter: { module: { course: { teacher: { id: userId } } } } + }, + }).run(); + return new PaginatedChatRoomResponseDto( + chatRooms.data, + chatRooms.meta.total, + chatRooms.meta.pageSize, + chatRooms.meta.currentPage, + ); + } - async findOne(options: FindOneOptions): Promise { - try { - return await this.chatRoomRepository.findOne(options); - } catch (error) { - if (error instanceof Error) throw new NotFoundException(error.message); + async findOne(options: FindOneOptions): Promise { + try { + return await this.chatRoomRepository.findOne(options); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); + } } - } - async update( - criteria: FindOptionsWhere, - updateChatRoomDto: UpdateChatRoomDto, - ): Promise { - try { - await this.chatRoomRepository.update(criteria, updateChatRoomDto); - return await this.findOne({ where: criteria }); - } catch (error) { - if (error instanceof Error) throw new NotFoundException(error.message); + async update( + criteria: FindOptionsWhere, + updateChatRoomDto: UpdateChatRoomDto, + ): Promise { + try { + await this.chatRoomRepository.update(criteria, updateChatRoomDto); + return await this.findOne({ where: criteria }); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); + } } - } - async delete(criteria: FindOptionsWhere): Promise { - try { - await this.chatRoomRepository.delete(criteria); - } catch (error) { - if (error instanceof Error) throw new NotFoundException(error.message); + async delete(criteria: FindOptionsWhere): Promise { + try { + await this.chatRoomRepository.delete(criteria); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); + } } - } } diff --git a/src/chat-room/dtos/paginated-chat-room-response.dto.ts b/src/chat-room/dtos/paginated-chat-room-response.dto.ts index 62597ca..db94bd7 100644 --- a/src/chat-room/dtos/paginated-chat-room-response.dto.ts +++ b/src/chat-room/dtos/paginated-chat-room-response.dto.ts @@ -2,66 +2,75 @@ import { ApiProperty } from '@nestjs/swagger'; import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; import { ChatRoom } from '../chat-room.entity'; import { ChatRoomType, ChatRoomStatus } from '../enums'; +import { ChapterResponseDto } from 'src/chapter/dtos/chapter-response.dto'; export class ChatRoomResponseDto { - @ApiProperty({ - description: 'ChatRoom ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }) - id: string; + @ApiProperty({ + description: 'ChatRoom ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + id: string; - @ApiProperty({ - description: 'ChatRoom title', - type: String, - example: 'Chat Room 1', - }) - title: string; + @ApiProperty({ + description: 'Chapter', + type: String, + example: 'Chat Room 1', + }) + chapter: ChapterResponseDto; - @ApiProperty({ - description: 'ChatRoom status', - type: String, - example: ChatRoomStatus.ACTIVE, - enum: ChatRoomStatus, - }) - status: ChatRoomStatus; + @ApiProperty({ + description: 'ChatRoom title', + type: String, + example: 'Chat Room 1', + }) + title: string; - @ApiProperty({ - description: 'ChatRoom type', - type: String, - example: ChatRoomType.QUESTION, - enum: ChatRoomType, - }) - type: ChatRoomType; + @ApiProperty({ + description: 'ChatRoom status', + type: String, + example: ChatRoomStatus.ACTIVE, + enum: ChatRoomStatus, + }) + status: ChatRoomStatus; - @ApiProperty({ - description: 'ChatRoom participant count', - type: Number, - example: 5, - }) - paticipantCount: number; + @ApiProperty({ + description: 'ChatRoom type', + type: String, + example: ChatRoomType.QUESTION, + enum: ChatRoomType, + }) + type: ChatRoomType; - constructor(chatRoom: ChatRoom) { - this.id = chatRoom.id; - this.title = chatRoom.title; - this.status = chatRoom.status; - this.type = chatRoom.type; - this.paticipantCount = chatRoom.participantCount; - } + @ApiProperty({ + description: 'ChatRoom participant count', + type: Number, + example: 5, + }) + paticipantCount: number; + + constructor(chatRoom: ChatRoom) { + this.id = chatRoom.id; + this.title = chatRoom.title; + this.status = chatRoom.status; + this.type = chatRoom.type; + this.paticipantCount = chatRoom.participantCount; + this.chapter = new ChapterResponseDto(chatRoom.chapter); + } } export class PaginatedChatRoomResponseDto extends PaginatedResponse( - ChatRoomResponseDto, + ChatRoomResponseDto, ) { - constructor( - chatRooms: ChatRoom[], - total: number, - pageSize: number, - currentPage: number, - ) { - const chatRoomDtos = chatRooms.map( - (chatRoom) => new ChatRoomResponseDto(chatRoom), - ); - super(chatRoomDtos, total, pageSize, currentPage); - } + constructor( + chatRooms: ChatRoom[], + total: number, + pageSize: number, + currentPage: number, + ) { + const chatRoomDtos = chatRooms.map( + (chatRoom) => new ChatRoomResponseDto(chatRoom), + ); + super(chatRoomDtos, total, pageSize, currentPage); + } } diff --git a/src/chat-room/guards/chat-room-ownership.guard.ts b/src/chat-room/guards/chat-room-ownership.guard.ts index ea82d48..944a739 100644 --- a/src/chat-room/guards/chat-room-ownership.guard.ts +++ b/src/chat-room/guards/chat-room-ownership.guard.ts @@ -1,36 +1,58 @@ import { - Injectable, - CanActivate, - ExecutionContext, - NotFoundException, + Injectable, + CanActivate, + ExecutionContext, + NotFoundException, + ForbiddenException, } from '@nestjs/common'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { ChatRoomService } from '../chat-room.service'; import { EnrollmentService } from 'src/enrollment/enrollment.service'; +import { Role } from 'src/shared/enums'; @Injectable() export class ChatRoomOwnershipGuard implements CanActivate { - constructor( - private readonly chatRoomService: ChatRoomService, - private readonly enrollmentRepository: EnrollmentService, - ) {} + constructor( + private readonly chatRoomService: ChatRoomService, + private readonly enrollmentRepository: EnrollmentService, + ) { } - async canActivate(context: ExecutionContext): Promise { - const request: AuthenticatedRequest = context.switchToHttp().getRequest(); - const userId = request.user.id; - const chatRoomId = request.params.id; - - const chatRoom = await this.chatRoomService.findOne({ - where: { id: chatRoomId }, - }); - const course = chatRoom.chapter.module.course; - const enrollment = await this.enrollmentRepository.findOne({ - user: { id: userId }, - course: { id: course.id }, - }); - if (!enrollment) - throw new NotFoundException('You are not enrolled in this course'); - - return true; - } + async canActivate(context: ExecutionContext): Promise { + const request: AuthenticatedRequest = context.switchToHttp().getRequest(); + const userId = request.user.id; + const chatRoomId = request.params.id; + switch (request.user.role) { + case Role.STUDENT: + const chatRoom = await this.chatRoomService.findOne({ + where: { id: chatRoomId }, + }); + const course = chatRoom.chapter.module.course; + const enrollment = await this.enrollmentRepository.findOne({ + user: { id: request.user.id }, + course: { id: course.id }, + }); + if (!enrollment) + throw new NotFoundException('You are not enrolled in this course'); + break; + case Role.TEACHER: + const teacher = await this.chatRoomService.findOne({ + where: { id: chatRoomId }, + relations: { + chapter: { + module: { + course: { + teacher: true, + }, + }, + }, + } + }); + if (teacher.chapter.module.course.teacher.id !== userId) + throw new NotFoundException('You are not the owner of this chat room'); + break; + default: + throw new ForbiddenException('You are not allowed to access this resource'); + } + return true; + } } diff --git a/src/course/course.service.ts b/src/course/course.service.ts index d27d128..ba4d73c 100644 --- a/src/course/course.service.ts +++ b/src/course/course.service.ts @@ -1,191 +1,191 @@ import { - BadRequestException, - Inject, - Injectable, - NotFoundException, + BadRequestException, + Inject, + Injectable, + NotFoundException, } from '@nestjs/common'; import { CourseStatus, Role } from 'src/shared/enums'; import { createPagination } from 'src/shared/pagination'; import { FindOneOptions, FindOptionsWhere, ILike, Repository } from 'typeorm'; import { Course } from './course.entity'; import { - CreateCourseDto, - PaginatedCourseResponeDto, - UpdateCourseDto, + CreateCourseDto, + PaginatedCourseResponeDto, + UpdateCourseDto, } from './dtos/index'; @Injectable() export class CourseService { - constructor( - @Inject('CourseRepository') - private readonly courseRepository: Repository, - ) {} - - async findAll({ - page = 1, - limit = 20, - search = '', - userId, - role, - }: { - page?: number; - limit?: number; - search?: string; - userId: string; - role: Role; - }): Promise { - const { find } = await createPagination(this.courseRepository, { - page, - limit, - }); - - const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; - const whereCondition = this.buildWhereCondition(userId, role, baseSearch); - - const courses = await find({ - where: whereCondition, - relations: { - teacher: true, - category: true, - }, - }).run(); - - return courses; - } - - async findOne( - userId: string, - role: Role, - options: FindOneOptions, - ): Promise { - const baseWhere = options.where as FindOptionsWhere; - const whereCondition = this.buildWhereCondition(userId, role, baseWhere); - - const course = await this.courseRepository.findOne({ - where: whereCondition, - relations: { - teacher: true, - category: true, - }, - }); - - if (!course) { - throw new NotFoundException('Course not found'); + constructor( + @Inject('CourseRepository') + private readonly courseRepository: Repository, + ) { } + + async findAll({ + page = 1, + limit = 20, + search = '', + userId, + role, + }: { + page?: number; + limit?: number; + search?: string; + userId: string; + role: Role; + }): Promise { + const { find } = await createPagination(this.courseRepository, { + page, + limit, + }); + + const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; + const whereCondition = this.buildWhereCondition(userId, role, baseSearch); + + const courses = await find({ + where: whereCondition, + relations: { + teacher: true, + category: true, + }, + }).run(); + + return courses; } - return course; - } - - async create( - userId: string, - createCourseDto: CreateCourseDto, - ): Promise { - try { - const course = this.courseRepository.create({ - ...createCourseDto, - teacher: { id: userId }, - category: { id: createCourseDto.categoryId }, - }); - - return this.courseRepository.save(course); - } catch (error) { - if (error instanceof Error) { - throw new BadRequestException(error.message); - } + async findOne( + userId: string, + role: Role, + options: FindOneOptions, + ): Promise { + const baseWhere = options.where as FindOptionsWhere; + const whereCondition = this.buildWhereCondition(userId, role, baseWhere); + + const course = await this.courseRepository.findOne({ + where: whereCondition, + relations: { + teacher: true, + category: true, + }, + }); + + if (!course) { + throw new NotFoundException('Course not found'); + } + + return course; } - } - async update(id: string, updateCourseDto: UpdateCourseDto): Promise { - const existingCourse = await this.courseRepository.findOne({ - where: { id }, - relations: { - teacher: true, - category: true, - }, - }); - - if (!existingCourse) { - throw new NotFoundException('Course not found'); + + async create( + userId: string, + createCourseDto: CreateCourseDto, + ): Promise { + try { + const course = this.courseRepository.create({ + ...createCourseDto, + teacher: { id: userId }, + category: { id: createCourseDto.categoryId }, + }); + + return this.courseRepository.save(course); + } catch (error) { + if (error instanceof Error) { + throw new BadRequestException(error.message); + } + } + } + async update(id: string, updateCourseDto: UpdateCourseDto): Promise { + const existingCourse = await this.courseRepository.findOne({ + where: { id }, + relations: { + teacher: true, + category: true, + }, + }); + + if (!existingCourse) { + throw new NotFoundException('Course not found'); + } + + this.validateStatusTransition( + existingCourse.status, + updateCourseDto.status, + ); + + const updatedCourse = await this.courseRepository.save({ + ...existingCourse, + ...updateCourseDto, + category: { id: updateCourseDto.categoryId }, + }); + + return updatedCourse; } - this.validateStatusTransition( - existingCourse.status, - updateCourseDto.status, - ); - - const updatedCourse = await this.courseRepository.save({ - ...existingCourse, - ...updateCourseDto, - category: { id: updateCourseDto.categoryId }, - }); - - return updatedCourse; - } - - async delete(id: string): Promise { - try { - await this.courseRepository.delete(id); - } catch (error) { - throw new NotFoundException('Course not found'); + async delete(id: string): Promise { + try { + await this.courseRepository.delete(id); + } catch (error) { + throw new NotFoundException('Course not found'); + } } - } - - private buildWhereCondition( - userId: string, - role: Role, - baseCondition: FindOptionsWhere = {}, - ): FindOptionsWhere | FindOptionsWhere[] { - const conditions: Record< - Role, - () => FindOptionsWhere | FindOptionsWhere[] - > = { - [Role.STUDENT]: () => ({ - ...baseCondition, - status: CourseStatus.PUBLISHED, - }), - [Role.TEACHER]: () => [ - { - ...baseCondition, - status: CourseStatus.PUBLISHED, - }, - { - ...baseCondition, - teacher: { id: userId }, - }, - ], - [Role.ADMIN]: () => baseCondition, - }; - - const buildCondition = conditions[role]; - - if (!buildCondition) { - throw new BadRequestException('Invalid role'); + + private buildWhereCondition( + userId: string, + role: Role, + baseCondition: FindOptionsWhere = {}, + ): FindOptionsWhere | FindOptionsWhere[] { + const conditions: Record< + Role, + () => FindOptionsWhere | FindOptionsWhere[] + > = { + [Role.STUDENT]: () => ({ + ...baseCondition, + status: CourseStatus.PUBLISHED, + }), + [Role.TEACHER]: () => [ + { + ...baseCondition, + status: CourseStatus.PUBLISHED, + }, + { + ...baseCondition, + teacher: { id: userId }, + }, + ], + [Role.ADMIN]: () => baseCondition, + }; + + const buildCondition = conditions[role]; + + if (!buildCondition) { + throw new BadRequestException('Invalid role'); + } + + return buildCondition(); } - return buildCondition(); - } - - private validateStatusTransition( - currentStatus: CourseStatus, - newStatus?: CourseStatus, - ): void { - if (!newStatus) return; - - if ( - currentStatus !== CourseStatus.DRAFT && - newStatus === CourseStatus.DRAFT - ) { - throw new BadRequestException( - `Cannot change status back to draft when current status is ${currentStatus}`, - ); + private validateStatusTransition( + currentStatus: CourseStatus, + newStatus?: CourseStatus, + ): void { + if (!newStatus) return; + + if ( + currentStatus !== CourseStatus.DRAFT && + newStatus === CourseStatus.DRAFT + ) { + throw new BadRequestException( + `Cannot change status back to draft when current status is ${currentStatus}`, + ); + } + } + async validateOwnership(id: string, userId: string): Promise { + const course = await this.courseRepository.findOne({ + where: { id }, + relations: { teacher: true }, + }); + if (!course) throw new NotFoundException('Course not found'); + if (course.teacher.id !== userId) + throw new BadRequestException('You can only access your own courses'); } - } - async validateOwnership(id: string, userId: string): Promise { - const course = await this.courseRepository.findOne({ - where: { id }, - relations: { teacher: true }, - }); - if (!course) throw new NotFoundException('Course not found'); - if (course.teacher.id !== userId) - throw new BadRequestException('You can only access your own courses'); - } } From 828ff3d2f872f70682434d37cba0ed24691f1801 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Tue, 19 Nov 2024 14:28:15 +0700 Subject: [PATCH 085/155] feat: add AI access secret to environment config and implement chat message guard for authorization --- .env.example | 1 + src/auth/auth.service.ts | 164 +++++++------- src/chat-message/chat-message.controller.ts | 212 +++++++++--------- src/chat-message/chat-message.module.ts | 15 +- src/chat-message/chat-message.service.ts | 156 ++++++------- .../dtos/create-chat-message.dto.ts | 78 +++---- .../guards/create-chat-message.guard.ts | 71 ++++++ src/enrollment/enrollment.service.ts | 122 +++++----- src/shared/configs/dotenv.config.ts | 47 ++-- .../constants/global-config.constant.ts | 35 +-- src/user/user.service.ts | 106 ++++----- 11 files changed, 546 insertions(+), 461 deletions(-) create mode 100644 src/chat-message/guards/create-chat-message.guard.ts diff --git a/.env.example b/.env.example index 4875d03..9a8a75f 100644 --- a/.env.example +++ b/.env.example @@ -10,6 +10,7 @@ JWT_ACCESS_SECRET=+W5LRxRFUk8MKSHMeRYevRg JWT_REFRESH_SECRET=2Z7bB8GizN15kaHzB+H8Tg JWT_ACCESS_EXPIRATION=1d JWT_REFRESH_EXPIRATION=7d +JWT_AI_ACCESS_SECRET=2Z7bB8GizN15kaHzB+H8Tg AWS_ACCESS_KEY_ID=youraccesskey AWS_SECRET_ACCESS_KEY=yoursecretkey AWS_REGION=yourregion diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index ebd4ca6..c4cc0db 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -1,8 +1,8 @@ import { - Injectable, - NotFoundException, - BadRequestException, - InternalServerErrorException, + Injectable, + NotFoundException, + BadRequestException, + InternalServerErrorException, } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; @@ -18,88 +18,88 @@ import { UserStreakService } from 'src/user-streak/user-streak.service'; @Injectable() export class AuthService { - constructor( - private readonly configService: ConfigService, - private readonly jwtService: JwtService, - private readonly userService: UserService, - private readonly userStreakService: UserStreakService, - ) {} + constructor( + private readonly configService: ConfigService, + private readonly jwtService: JwtService, + private readonly userService: UserService, + private readonly userStreakService: UserStreakService, + ) { } - async login(loginDto: LoginDto): Promise { - const user = await this.userService.findOne({ - where: { email: loginDto.email }, - }); - if (!user) throw new NotFoundException('User not found'); - const isPasswordValid = await verify(user.password, loginDto.password); - if (!isPasswordValid) throw new BadRequestException('Invalid password'); - try { - const accessToken = this.generateAccessToken({ - id: user.id, - role: user.role, - }); - const refreshToken = this.generateRefreshToken(); - await this.userStreakService.update(user.id); - return { - accessToken, - refreshToken, - user: new UserResponseDto(user), - }; - } catch (error) { - if (error instanceof Error) - throw new InternalServerErrorException(error.message); + async login(loginDto: LoginDto): Promise { + const user = await this.userService.findOne({ + where: { email: loginDto.email }, + }); + if (!user) throw new NotFoundException('User not found'); + const isPasswordValid = await verify(user.password, loginDto.password); + if (!isPasswordValid) throw new BadRequestException('Invalid password'); + try { + const accessToken = this.generateAccessToken({ + id: user.id, + role: user.role, + }); + const refreshToken = this.generateRefreshToken(); + await this.userStreakService.update(user.id); + return { + accessToken, + refreshToken, + user: new UserResponseDto(user), + }; + } catch (error) { + if (error instanceof Error) + throw new InternalServerErrorException(error.message); + } } - } - async register(registerDto: RegisterDto): Promise { - const user = await this.userService.findOne({ - where: { email: registerDto.email }, - }); - if (user) throw new BadRequestException('User already exists'); - const hashedPassword = await hash(registerDto.password); - const createdUser = await this.userService.create({ - ...registerDto, - password: hashedPassword, - }); - try { - const accessToken = this.generateAccessToken({ - id: createdUser.id, - role: createdUser.role, - }); - const refreshToken = this.generateRefreshToken(); - await this.userStreakService.create(createdUser.id); - return { - accessToken, - refreshToken, - user: new UserResponseDto(createdUser), - }; - } catch (error) { - if (error instanceof Error) { - await this.userService.delete(createdUser.id); - throw new InternalServerErrorException(error.message); - } + async register(registerDto: RegisterDto): Promise { + const user = await this.userService.findOne({ + where: { email: registerDto.email }, + }); + if (user) throw new BadRequestException('User already exists'); + const hashedPassword = await hash(registerDto.password); + const createdUser = await this.userService.create({ + ...registerDto, + password: hashedPassword, + }); + try { + const accessToken = this.generateAccessToken({ + id: createdUser.id, + role: createdUser.role, + }); + const refreshToken = this.generateRefreshToken(); + await this.userStreakService.create(createdUser.id); + return { + accessToken, + refreshToken, + user: new UserResponseDto(createdUser), + }; + } catch (error) { + if (error instanceof Error) { + await this.userService.delete(createdUser.id); + throw new InternalServerErrorException(error.message); + } + } } - } - generateAccessToken(payload: JwtPayloadDto): string { - return this.jwtService.sign(payload, { - secret: this.configService.get(GLOBAL_CONFIG.JWT_ACCESS_SECRET), - expiresIn: this.configService.get( - GLOBAL_CONFIG.JWT_ACCESS_EXPIRATION, - ), - }); - } + private generateAccessToken(payload: JwtPayloadDto): string { + return this.jwtService.sign(payload, { + secret: this.configService.get(GLOBAL_CONFIG.JWT_ACCESS_SECRET), + expiresIn: this.configService.get( + GLOBAL_CONFIG.JWT_ACCESS_EXPIRATION, + ), + }); + } - generateRefreshToken(): string { - return this.jwtService.sign( - {}, - { - secret: this.configService.get( - GLOBAL_CONFIG.JWT_REFRESH_SECRET, - ), - expiresIn: this.configService.get( - GLOBAL_CONFIG.JWT_REFRESH_EXPIRATION, - ), - }, - ); - } + private generateRefreshToken(): string { + return this.jwtService.sign( + {}, + { + secret: this.configService.get( + GLOBAL_CONFIG.JWT_REFRESH_SECRET, + ), + expiresIn: this.configService.get( + GLOBAL_CONFIG.JWT_REFRESH_EXPIRATION, + ), + }, + ); + } } diff --git a/src/chat-message/chat-message.controller.ts b/src/chat-message/chat-message.controller.ts index 5a5bf35..db5e9f4 100644 --- a/src/chat-message/chat-message.controller.ts +++ b/src/chat-message/chat-message.controller.ts @@ -1,17 +1,18 @@ import { - Body, - Controller, - Delete, - Get, - HttpCode, - HttpStatus, - Injectable, - Param, - ParseUUIDPipe, - Patch, - Post, - Query, - Req, + Body, + Controller, + Delete, + Get, + HttpCode, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, + UseGuards, } from '@nestjs/common'; import { ChatMessageService } from './chat-message.service'; import { ApiTags, ApiBearerAuth, ApiResponse } from '@nestjs/swagger'; @@ -20,106 +21,109 @@ import { Roles } from 'src/shared/decorators/role.decorator'; import { Role } from 'src/shared/enums'; import { CreateChatMessageDto, ChatMessageResponseDto } from './dtos'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { CreateChatMessageGuard } from './guards/create-chat-message.guard'; +import { Public } from 'src/shared/decorators/public.decorator'; @Controller('chat-message') @Injectable() @ApiTags('Chat Message') @ApiBearerAuth() export class ChatMessageController { - constructor(private readonly chatMessageService: ChatMessageService) {} + constructor(private readonly chatMessageService: ChatMessageService) { } - @Get() - @ApiResponse({ - status: HttpStatus.OK, - description: 'Returns all chat messages', - type: ChatMessageResponseDto, - isArray: true, - }) - @Roles(Role.ADMIN) - async findAll( - @Query() query: PaginateQueryDto, - @Req() request: AuthenticatedRequest, - ) { - return await this.chatMessageService.findAll({ - userId: request.user.id, - ...query, - }); - } + @Get() + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all chat messages', + type: ChatMessageResponseDto, + isArray: true, + }) + @Roles(Role.ADMIN) + async findAll( + @Query() query: PaginateQueryDto, + @Req() request: AuthenticatedRequest, + ) { + return await this.chatMessageService.findAll({ + userId: request.user.id, + ...query, + }); + } - @Get(':id') - @ApiResponse({ - status: HttpStatus.OK, - description: 'Returns a chat message by id', - type: ChatMessageResponseDto, - }) - @Roles(Role.ADMIN) - async findOne( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - ) { - return await this.chatMessageService.findOne({ where: { id } }); - } + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns a chat message by id', + type: ChatMessageResponseDto, + }) + @Roles(Role.ADMIN) + async findOne( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ) { + return await this.chatMessageService.findOne({ where: { id } }); + } - @Post() - @ApiResponse({ - status: HttpStatus.CREATED, - description: 'Creates a chat message', - type: ChatMessageResponseDto, - }) - @Roles(Role.ADMIN) - @HttpCode(HttpStatus.CREATED) - async create( - @Body() createChatMessageDto: CreateChatMessageDto, - @Req() request: AuthenticatedRequest, - ) { - return await this.chatMessageService.create( - request.user.id, - createChatMessageDto, - ); - } + @Post() + @Public() + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Creates a chat message', + type: ChatMessageResponseDto, + }) + @HttpCode(HttpStatus.CREATED) + @UseGuards(CreateChatMessageGuard) + async create( + @Body() createChatMessageDto: CreateChatMessageDto, + @Req() request: AuthenticatedRequest, + ) { + return await this.chatMessageService.create( + request.user.id, + createChatMessageDto, + ); + } - @Patch(':id') - @ApiResponse({ - status: HttpStatus.OK, - description: 'Updates a chat message by id', - type: ChatMessageResponseDto, - }) - async update( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - @Body() updateChatMessageDto: CreateChatMessageDto, - ) { - return await this.chatMessageService.update({ id }, updateChatMessageDto); - } + @Patch(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Updates a chat message by id', + type: ChatMessageResponseDto, + }) + async update( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @Body() updateChatMessageDto: CreateChatMessageDto, + ) { + return await this.chatMessageService.update({ id }, updateChatMessageDto); + } - @Delete(':id') - @ApiResponse({ - status: HttpStatus.NO_CONTENT, - description: 'Deletes a chat message by id', - }) - @HttpCode(HttpStatus.NO_CONTENT) - async delete( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - ) { - await this.chatMessageService.delete({ id }); - } + @Delete(':id') + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Deletes a chat message by id', + }) + @HttpCode(HttpStatus.NO_CONTENT) + async delete( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ) { + await this.chatMessageService.delete({ id }); + } } diff --git a/src/chat-message/chat-message.module.ts b/src/chat-message/chat-message.module.ts index 423df73..ac9f451 100644 --- a/src/chat-message/chat-message.module.ts +++ b/src/chat-message/chat-message.module.ts @@ -5,10 +5,17 @@ import { ChatMessage } from './chat-message.entity'; import { ChatMessageController } from './chat-message.controller'; import { ChatMessageService } from './chat-message.service'; import { chatMessageProviders } from './chat-message.providers'; +import { ChatRoomModule } from 'src/chat-room/chat-room.module'; +import { EnrollmentModule } from 'src/enrollment/enrollment.module'; @Module({ - imports: [DatabaseModule, TypeOrmModule.forFeature([ChatMessage])], - controllers: [ChatMessageController], - providers: [...chatMessageProviders, ChatMessageService], + imports: [ + DatabaseModule, + TypeOrmModule.forFeature([ChatMessage]), + ChatRoomModule, + EnrollmentModule, + ], + controllers: [ChatMessageController], + providers: [...chatMessageProviders, ChatMessageService], }) -export class ChatMessageModule {} +export class ChatMessageModule { } diff --git a/src/chat-message/chat-message.service.ts b/src/chat-message/chat-message.service.ts index 59d4db2..ac5081b 100644 --- a/src/chat-message/chat-message.service.ts +++ b/src/chat-message/chat-message.service.ts @@ -1,98 +1,98 @@ import { - Injectable, - Inject, - InternalServerErrorException, - NotFoundException, + Injectable, + Inject, + InternalServerErrorException, + NotFoundException, } from '@nestjs/common'; import { Repository, FindOptionsWhere, FindOneOptions } from 'typeorm'; import { ChatMessage } from './chat-message.entity'; import { createPagination } from 'src/shared/pagination'; import { - UpdateChatMessageDto, - CreateChatMessageDto, - PaginatedChatMessageResponseDto, + UpdateChatMessageDto, + CreateChatMessageDto, + PaginatedChatMessageResponseDto, } from './dtos'; @Injectable() export class ChatMessageService { - constructor( - @Inject('ChatMessageRepository') - private readonly chatMessageRepository: Repository, - ) {} + constructor( + @Inject('ChatMessageRepository') + private readonly chatMessageRepository: Repository, + ) { } - async create( - userId: string, - createChatMessageDto: CreateChatMessageDto, - ): Promise { - try { - return await this.chatMessageRepository.save({ - ...createChatMessageDto, - user: { id: userId }, - chatRoomId: { id: createChatMessageDto.chatRoomId }, - reply: createChatMessageDto.replyId - ? { id: createChatMessageDto.replyId } - : null, - }); - } catch (error) { - if (error instanceof Error) - throw new InternalServerErrorException(error.message); + async create( + userId: string, + createChatMessageDto: CreateChatMessageDto, + ): Promise { + try { + return await this.chatMessageRepository.save({ + ...createChatMessageDto, + user: { id: userId }, + chatRoomId: { id: createChatMessageDto.chatRoomId }, + reply: createChatMessageDto.replyId + ? { id: createChatMessageDto.replyId } + : null, + }); + } catch (error) { + if (error instanceof Error) + throw new InternalServerErrorException(error.message); + } } - } - async findAll({ - userId, - page = 1, - limit = 20, - search = '', - }: { - userId: string; - page?: number; - limit?: number; - search?: string; - }): Promise { - const { find } = await createPagination(this.chatMessageRepository, { - page, - limit, - }); - const chatMessages = await find({ - where: { content: search, user: { id: userId } }, - }).run(); - return new PaginatedChatMessageResponseDto( - chatMessages.data, - chatMessages.meta.total, - chatMessages.meta.pageSize, - chatMessages.meta.currentPage, - ); - } + async findAll({ + userId, + page = 1, + limit = 20, + search = '', + }: { + userId: string; + page?: number; + limit?: number; + search?: string; + }): Promise { + const { find } = await createPagination(this.chatMessageRepository, { + page, + limit, + }); + const chatMessages = await find({ + where: { content: search, user: { id: userId } }, + }).run(); + return new PaginatedChatMessageResponseDto( + chatMessages.data, + chatMessages.meta.total, + chatMessages.meta.pageSize, + chatMessages.meta.currentPage, + ); + } - async findOne(options: FindOneOptions): Promise { - try { - return await this.chatMessageRepository.findOne(options); - } catch (error) { - if (error instanceof Error) throw new NotFoundException(error.message); + async findOne(options: FindOneOptions): Promise { + try { + return await this.chatMessageRepository.findOne(options); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); + } } - } - async update( - criteria: FindOptionsWhere, - updateChatMessageDto: UpdateChatMessageDto, - ): Promise { - try { - await this.chatMessageRepository.update(criteria, { - ...updateChatMessageDto, - isEdited: true, - }); - return await this.findOne({ where: criteria }); - } catch (error) { - if (error instanceof Error) throw new NotFoundException(error.message); + async update( + criteria: FindOptionsWhere, + updateChatMessageDto: UpdateChatMessageDto, + ): Promise { + try { + await this.chatMessageRepository.update(criteria, { + ...updateChatMessageDto, + isEdited: true, + }); + return await this.findOne({ where: criteria }); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); + } } - } - async delete(criteria: FindOptionsWhere): Promise { - try { - await this.chatMessageRepository.delete(criteria); - } catch (error) { - if (error instanceof Error) throw new NotFoundException(error.message); + async delete(criteria: FindOptionsWhere): Promise { + try { + await this.chatMessageRepository.delete(criteria); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); + } } - } } diff --git a/src/chat-message/dtos/create-chat-message.dto.ts b/src/chat-message/dtos/create-chat-message.dto.ts index 3447038..3dc3a2b 100644 --- a/src/chat-message/dtos/create-chat-message.dto.ts +++ b/src/chat-message/dtos/create-chat-message.dto.ts @@ -1,49 +1,49 @@ import { - IsString, - IsNotEmpty, - IsOptional, - IsUUID, - IsEnum, + IsString, + IsNotEmpty, + IsOptional, + IsUUID, + IsEnum, } from 'class-validator'; import { ChatMessageType } from '../enums/chat-message-type.enum'; import { ApiProperty } from '@nestjs/swagger'; export class CreateChatMessageDto { - @IsString() - @IsNotEmpty() - @ApiProperty({ - description: 'ChatMessage content', - type: String, - example: 'Hello World!', - }) - content: string; + @IsString() + @IsNotEmpty() + @ApiProperty({ + description: 'ChatMessage content', + type: String, + example: 'Hello World!', + }) + content: string; - @IsOptional() - @IsUUID('4') - @ApiProperty({ - description: 'ChatMessage reply ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - required: false, - }) - replyId?: string; + @IsOptional() + @IsUUID('4') + @ApiProperty({ + description: 'ChatMessage reply ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + required: false, + }) + replyId?: string; - @IsEnum(ChatMessageType) - @IsNotEmpty() - @ApiProperty({ - description: 'ChatMessage type', - type: String, - example: ChatMessageType.TEXT, - enum: ChatMessageType, - }) - type: ChatMessageType; + @IsEnum(ChatMessageType) + @IsNotEmpty() + @ApiProperty({ + description: 'ChatMessage type', + type: String, + example: ChatMessageType.TEXT, + enum: ChatMessageType, + }) + type: ChatMessageType; - @IsNotEmpty() - @IsUUID('4') - @ApiProperty({ - description: 'ChatRoom ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }) - chatRoomId: string; + @IsNotEmpty() + @IsUUID('4') + @ApiProperty({ + description: 'ChatRoom ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + chatRoomId: string; } diff --git a/src/chat-message/guards/create-chat-message.guard.ts b/src/chat-message/guards/create-chat-message.guard.ts new file mode 100644 index 0000000..db5542d --- /dev/null +++ b/src/chat-message/guards/create-chat-message.guard.ts @@ -0,0 +1,71 @@ +import { + Injectable, + CanActivate, + ExecutionContext, + NotFoundException, + ForbiddenException, + UnauthorizedException, +} from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { ConfigService } from '@nestjs/config'; +import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; +import { Request } from 'express'; +import { JwtPayloadDto } from 'src/auth/dtos/jwt-payload.dto'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { Role } from 'src/shared/enums'; +import { CreateChatMessageDto } from '../dtos'; +import { ChatRoomService } from 'src/chat-room/chat-room.service'; +import { EnrollmentService } from 'src/enrollment/enrollment.service'; + +@Injectable() +export class CreateChatMessageGuard implements CanActivate { + constructor( + private readonly jwtService: JwtService, + private readonly configService: ConfigService, + private readonly chatRoomService: ChatRoomService, + private readonly enrollmentService: EnrollmentService, + ) { } + + async canActivate(context: ExecutionContext): Promise { + const request: AuthenticatedRequest = context.switchToHttp().getRequest(); + const createChatMessageDto = request.body as CreateChatMessageDto; + const token = this.extractTokenFromHeader(request); + if (!token) throw new UnauthorizedException('Unauthorized access'); + try { + await this.jwtService.verifyAsync(token, { + secret: this.configService.get(GLOBAL_CONFIG.JWT_AI_ACCESS_SECRET), + }); + return true; + } catch { } + try { + request.user = await this.jwtService.verifyAsync(token, { + secret: this.configService.get(GLOBAL_CONFIG.JWT_ACCESS_SECRET), + }); + } catch (error) { + throw new UnauthorizedException('Unauthorized access'); + } + if (request.user.role !== Role.STUDENT) + throw new ForbiddenException('Forbidden access'); + const chatRoom = await this.chatRoomService.findOne({ + where: { id: createChatMessageDto.chatRoomId }, + relations: { + chapter: { + module: { + course: true, + }, + } + } + }); + const courseId = chatRoom.chapter.module.course.id; + await this.enrollmentService.findOne({ + user: { id: request.user.id }, + course: { id: courseId }, + }); + return true; + } + + private extractTokenFromHeader(request: Request): string | undefined { + const [type, token] = request.headers.authorization?.split(' ') ?? []; + return type === 'Bearer' ? token : undefined; + } +} diff --git a/src/enrollment/enrollment.service.ts b/src/enrollment/enrollment.service.ts index 52394fb..ddb1bef 100644 --- a/src/enrollment/enrollment.service.ts +++ b/src/enrollment/enrollment.service.ts @@ -1,7 +1,7 @@ import { - BadRequestException, - Injectable, - NotFoundException, + BadRequestException, + Injectable, + NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { createPagination } from 'src/shared/pagination'; @@ -14,73 +14,73 @@ import { EnrollmentStatus } from './enums/enrollment-status.enum'; @Injectable() export class EnrollmentService { - constructor( - @InjectRepository(Enrollment) - private readonly enrollmentRepository: Repository, - ) {} + constructor( + @InjectRepository(Enrollment) + private readonly enrollmentRepository: Repository, + ) { } - async findAll({ - page = 1, - limit = 20, - }: { - page?: number; - limit?: number; - }): Promise { - const { find } = await createPagination(this.enrollmentRepository, { - page, - limit, - }); + async findAll({ + page = 1, + limit = 20, + }: { + page?: number; + limit?: number; + }): Promise { + const { find } = await createPagination(this.enrollmentRepository, { + page, + limit, + }); - const enrollments = await find({ - relations: { - user: true, - course: true, - }, - }).run(); + const enrollments = await find({ + relations: { + user: true, + course: true, + }, + }).run(); - return enrollments; - } + return enrollments; + } - async findOne(where: FindOptionsWhere): Promise { - const options: FindOneOptions = { - where, - relations: { - user: true, - course: true, - }, - }; + async findOne(where: FindOptionsWhere): Promise { + const options: FindOneOptions = { + where, + relations: { + user: true, + course: true, + }, + }; - const enrollment = await this.enrollmentRepository.findOne(options); + const enrollment = await this.enrollmentRepository.findOne(options); - if (!enrollment) throw new NotFoundException('Enrollment not found'); + if (!enrollment) throw new NotFoundException('Enrollment not found'); - return enrollment; - } + return enrollment; + } - async create(createEnrollmentDto: CreateEnrollmentDto): Promise { - const enrollment = this.enrollmentRepository.create(createEnrollmentDto); - await this.enrollmentRepository.save(enrollment); + async create(createEnrollmentDto: CreateEnrollmentDto): Promise { + const enrollment = this.enrollmentRepository.create(createEnrollmentDto); + await this.enrollmentRepository.save(enrollment); - return enrollment; - } + return enrollment; + } - async update( - id: string, - updateEnrollmentDto: UpdateEnrollmentDto, - ): Promise { - const enrollment = await this.findOne({ - status: EnrollmentStatus.ACTIVE, - id, - }); - this.enrollmentRepository.merge(enrollment, updateEnrollmentDto); - await this.enrollmentRepository.save(enrollment); - return enrollment; - } + async update( + id: string, + updateEnrollmentDto: UpdateEnrollmentDto, + ): Promise { + const enrollment = await this.findOne({ + status: EnrollmentStatus.ACTIVE, + id, + }); + this.enrollmentRepository.merge(enrollment, updateEnrollmentDto); + await this.enrollmentRepository.save(enrollment); + return enrollment; + } - async remove(id: string): Promise { - const enrollment = await this.findOne({ - id, - }); - await this.enrollmentRepository.remove(enrollment); - } + async remove(id: string): Promise { + const enrollment = await this.findOne({ + id, + }); + await this.enrollmentRepository.remove(enrollment); + } } diff --git a/src/shared/configs/dotenv.config.ts b/src/shared/configs/dotenv.config.ts index 14f5617..20be8a0 100644 --- a/src/shared/configs/dotenv.config.ts +++ b/src/shared/configs/dotenv.config.ts @@ -2,27 +2,28 @@ import * as Joi from 'joi'; import { Environment } from '../enums/environment.enum'; export const dotenvConfig = Joi.object({ - PORT: Joi.number().required(), - NODE_ENV: Joi.string() - .valid(...Object.values(Environment)) - .default(Environment.DEVELOPMENT), - IS_DEVELOPMENT: Joi.boolean().when('NODE_ENV', { - is: Joi.equal(Environment.DEVELOPMENT), - then: Joi.boolean().default(true), - otherwise: Joi.boolean().default(false), - }), - DB_HOST: Joi.string().required(), - DB_PORT: Joi.number().required(), - DB_USERNAME: Joi.string().required(), - DB_PASSWORD: Joi.string().required(), - DB_DATABASE: Joi.string().required(), - CORS_ALLOW_ORIGIN: Joi.string().required(), - JWT_ACCESS_SECRET: Joi.string().required(), - JWT_REFRESH_SECRET: Joi.string().required(), - JWT_ACCESS_EXPIRATION: Joi.string().required(), - JWT_REFRESH_EXPIRATION: Joi.string().required(), - AWS_ACCESS_KEY_ID: Joi.string().required(), - AWS_SECRET_ACCESS_KEY: Joi.string().required(), - AWS_REGION: Joi.string().required(), - AWS_BUCKET_NAME: Joi.string().required(), + PORT: Joi.number().required(), + NODE_ENV: Joi.string() + .valid(...Object.values(Environment)) + .default(Environment.DEVELOPMENT), + IS_DEVELOPMENT: Joi.boolean().when('NODE_ENV', { + is: Joi.equal(Environment.DEVELOPMENT), + then: Joi.boolean().default(true), + otherwise: Joi.boolean().default(false), + }), + DB_HOST: Joi.string().required(), + DB_PORT: Joi.number().required(), + DB_USERNAME: Joi.string().required(), + DB_PASSWORD: Joi.string().required(), + DB_DATABASE: Joi.string().required(), + CORS_ALLOW_ORIGIN: Joi.string().required(), + JWT_ACCESS_SECRET: Joi.string().required(), + JWT_REFRESH_SECRET: Joi.string().required(), + JWT_ACCESS_EXPIRATION: Joi.string().required(), + JWT_REFRESH_EXPIRATION: Joi.string().required(), + AWS_ACCESS_KEY_ID: Joi.string().required(), + AWS_SECRET_ACCESS_KEY: Joi.string().required(), + AWS_REGION: Joi.string().required(), + AWS_BUCKET_NAME: Joi.string().required(), + JWT_AI_ACCESS_SECRET: Joi.string().required(), }); diff --git a/src/shared/constants/global-config.constant.ts b/src/shared/constants/global-config.constant.ts index 6a59d15..5b259e9 100644 --- a/src/shared/constants/global-config.constant.ts +++ b/src/shared/constants/global-config.constant.ts @@ -1,19 +1,20 @@ export const GLOBAL_CONFIG = { - PORT: 'PORT', - NODE_ENV: 'NODE_ENV', - IS_DEVELOPMENT: 'IS_DEVELOPMENT', - DB_HOST: 'DB_HOST', - DB_PORT: 'DB_PORT', - DB_USERNAME: 'DB_USERNAME', - DB_PASSWORD: 'DB_PASSWORD', - DB_DATABASE: 'DB_DATABASE', - CORS_ALLOW_ORIGIN: 'CORS_ALLOW_ORIGIN', - JWT_ACCESS_SECRET: 'JWT_ACCESS_SECRET', - JWT_REFRESH_SECRET: 'JWT_REFRESH_SECRET', - JWT_ACCESS_EXPIRATION: 'JWT_ACCESS_EXPIRATION', - JWT_REFRESH_EXPIRATION: 'JWT_REFRESH_EXPIRATION', - AWS_ACCESS_KEY_ID: 'AWS_ACCESS_KEY_ID', - AWS_SECRET_ACCESS_KEY: 'AWS_SECRET_ACCESS_KEY', - AWS_REGION: 'AWS_REGION', - AWS_BUCKET_NAME: 'AWS_BUCKET_NAME', + PORT: 'PORT', + NODE_ENV: 'NODE_ENV', + IS_DEVELOPMENT: 'IS_DEVELOPMENT', + DB_HOST: 'DB_HOST', + DB_PORT: 'DB_PORT', + DB_USERNAME: 'DB_USERNAME', + DB_PASSWORD: 'DB_PASSWORD', + DB_DATABASE: 'DB_DATABASE', + CORS_ALLOW_ORIGIN: 'CORS_ALLOW_ORIGIN', + JWT_ACCESS_SECRET: 'JWT_ACCESS_SECRET', + JWT_REFRESH_SECRET: 'JWT_REFRESH_SECRET', + JWT_ACCESS_EXPIRATION: 'JWT_ACCESS_EXPIRATION', + JWT_REFRESH_EXPIRATION: 'JWT_REFRESH_EXPIRATION', + AWS_ACCESS_KEY_ID: 'AWS_ACCESS_KEY_ID', + AWS_SECRET_ACCESS_KEY: 'AWS_SECRET_ACCESS_KEY', + AWS_REGION: 'AWS_REGION', + AWS_BUCKET_NAME: 'AWS_BUCKET_NAME', + JWT_AI_ACCESS_SECRET: 'JWT_AI_ACCESS_SECRET', }; diff --git a/src/user/user.service.ts b/src/user/user.service.ts index a21cfd7..dac42d5 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,8 +1,8 @@ import { - BadRequestException, - Inject, - Injectable, - NotFoundException, + BadRequestException, + Inject, + Injectable, + NotFoundException, } from '@nestjs/common'; import { createPagination } from 'src/shared/pagination'; import { FindOneOptions, ILike, Repository } from 'typeorm'; @@ -13,63 +13,63 @@ import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity @Injectable() export class UserService { - constructor( - @Inject('UserRepository') - private readonly userRepository: Repository, - ) {} + constructor( + @Inject('UserRepository') + private readonly userRepository: Repository, + ) { } - async findAll({ - page = 1, - limit = 20, - search = '', - }: { - page?: number; - limit?: number; - search?: string; - }): Promise { - const { find } = await createPagination(this.userRepository, { - page, - limit, - }); + async findAll({ + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }): Promise { + const { find } = await createPagination(this.userRepository, { + page, + limit, + }); - const users = await find({ - where: { email: ILike(`%${search}%`) }, - }).run(); + const users = await find({ + where: { email: ILike(`%${search}%`) }, + }).run(); - return users; - } + return users; + } - async findOne(options: FindOneOptions): Promise { - const user = this.userRepository.findOne(options); - if (!user) throw new NotFoundException('User not found'); - return user; - } + async findOne(options: FindOneOptions): Promise { + const user = this.userRepository.findOne(options); + if (!user) throw new NotFoundException('User not found'); + return user; + } - async create(createUserDto: CreateUserDto): Promise { - try { - return this.userRepository.save(createUserDto); - } catch (error) { - if (error instanceof Error) throw new BadRequestException(error.message); + async create(createUserDto: CreateUserDto): Promise { + try { + return this.userRepository.save(createUserDto); + } catch (error) { + if (error instanceof Error) throw new BadRequestException(error.message); + } } - } - async update( - id: string, - partialEntity: QueryDeepPartialEntity, - ): Promise { - try { - await this.userRepository.update(id, partialEntity); - return await this.findOne({ where: { id } }); - } catch (error) { - if (error instanceof Error) throw new NotFoundException('User not found'); + async update( + id: string, + partialEntity: QueryDeepPartialEntity, + ): Promise { + try { + await this.userRepository.update(id, partialEntity); + return await this.findOne({ where: { id } }); + } catch (error) { + if (error instanceof Error) throw new NotFoundException('User not found'); + } } - } - async delete(id: string): Promise { - try { - await this.userRepository.delete(id); - } catch (error) { - if (error instanceof Error) throw new NotFoundException('User not found'); + async delete(id: string): Promise { + try { + await this.userRepository.delete(id); + } catch (error) { + if (error instanceof Error) throw new NotFoundException('User not found'); + } } - } } From f0b1ff4a8ba9e570b437809109c358511eecb49c Mon Sep 17 00:00:00 2001 From: ganthepro Date: Tue, 19 Nov 2024 15:46:56 +0700 Subject: [PATCH 086/155] feat: update chat message types to lowercase, enhance enrollment relationships, and improve error handling in services --- src/chat-message/chat-message.controller.ts | 32 +-- src/chat-message/chat-message.module.ts | 5 +- src/chat-message/chat-message.service.ts | 15 +- .../enums/chat-message-type.enum.ts | 6 +- .../guards/create-chat-message.guard.ts | 65 ++---- src/chat-room/chat-room.controller.ts | 54 ++++- src/chat-room/chat-room.module.ts | 4 +- src/chat-room/chat-room.service.ts | 9 +- .../guards/chat-room-ownership.guard.ts | 11 +- src/enrollment/enrollment.controller.ts | 195 +++++++++--------- src/enrollment/enrollment.entity.ts | 4 +- src/enrollment/enrollment.service.ts | 20 +- src/user/user.service.ts | 80 +++---- 13 files changed, 266 insertions(+), 234 deletions(-) diff --git a/src/chat-message/chat-message.controller.ts b/src/chat-message/chat-message.controller.ts index db5e9f4..624ba38 100644 --- a/src/chat-message/chat-message.controller.ts +++ b/src/chat-message/chat-message.controller.ts @@ -16,38 +16,20 @@ import { } from '@nestjs/common'; import { ChatMessageService } from './chat-message.service'; import { ApiTags, ApiBearerAuth, ApiResponse } from '@nestjs/swagger'; -import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { Roles } from 'src/shared/decorators/role.decorator'; import { Role } from 'src/shared/enums'; import { CreateChatMessageDto, ChatMessageResponseDto } from './dtos'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { CreateChatMessageGuard } from './guards/create-chat-message.guard'; -import { Public } from 'src/shared/decorators/public.decorator'; @Controller('chat-message') @Injectable() @ApiTags('Chat Message') @ApiBearerAuth() export class ChatMessageController { - constructor(private readonly chatMessageService: ChatMessageService) { } - - @Get() - @ApiResponse({ - status: HttpStatus.OK, - description: 'Returns all chat messages', - type: ChatMessageResponseDto, - isArray: true, - }) - @Roles(Role.ADMIN) - async findAll( - @Query() query: PaginateQueryDto, - @Req() request: AuthenticatedRequest, - ) { - return await this.chatMessageService.findAll({ - userId: request.user.id, - ...query, - }); - } + constructor( + private readonly chatMessageService: ChatMessageService, + ) { } @Get(':id') @ApiResponse({ @@ -70,7 +52,6 @@ export class ChatMessageController { } @Post() - @Public() @ApiResponse({ status: HttpStatus.CREATED, description: 'Creates a chat message', @@ -78,14 +59,16 @@ export class ChatMessageController { }) @HttpCode(HttpStatus.CREATED) @UseGuards(CreateChatMessageGuard) + @Roles(Role.STUDENT, Role.ADMIN) async create( @Body() createChatMessageDto: CreateChatMessageDto, @Req() request: AuthenticatedRequest, ) { - return await this.chatMessageService.create( + const chatMessage = await this.chatMessageService.create( request.user.id, createChatMessageDto, ); + return new ChatMessageResponseDto(chatMessage); } @Patch(':id') @@ -105,7 +88,8 @@ export class ChatMessageController { id: string, @Body() updateChatMessageDto: CreateChatMessageDto, ) { - return await this.chatMessageService.update({ id }, updateChatMessageDto); + const chatMessage = await this.chatMessageService.update({ id }, updateChatMessageDto); + return new ChatMessageResponseDto(chatMessage); } @Delete(':id') diff --git a/src/chat-message/chat-message.module.ts b/src/chat-message/chat-message.module.ts index ac9f451..4af7ce2 100644 --- a/src/chat-message/chat-message.module.ts +++ b/src/chat-message/chat-message.module.ts @@ -1,4 +1,4 @@ -import { Module } from '@nestjs/common'; +import { Module, forwardRef } from '@nestjs/common'; import { DatabaseModule } from 'src/database/database.module'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ChatMessage } from './chat-message.entity'; @@ -12,10 +12,11 @@ import { EnrollmentModule } from 'src/enrollment/enrollment.module'; imports: [ DatabaseModule, TypeOrmModule.forFeature([ChatMessage]), - ChatRoomModule, + forwardRef(() => ChatRoomModule), EnrollmentModule, ], controllers: [ChatMessageController], providers: [...chatMessageProviders, ChatMessageService], + exports: [ChatMessageService], }) export class ChatMessageModule { } diff --git a/src/chat-message/chat-message.service.ts b/src/chat-message/chat-message.service.ts index ac5081b..03d8b80 100644 --- a/src/chat-message/chat-message.service.ts +++ b/src/chat-message/chat-message.service.ts @@ -25,10 +25,11 @@ export class ChatMessageService { createChatMessageDto: CreateChatMessageDto, ): Promise { try { + console.log(createChatMessageDto.chatRoomId); return await this.chatMessageRepository.save({ ...createChatMessageDto, user: { id: userId }, - chatRoomId: { id: createChatMessageDto.chatRoomId }, + chatRoom: { id: createChatMessageDto.chatRoomId }, reply: createChatMessageDto.replyId ? { id: createChatMessageDto.replyId } : null, @@ -40,22 +41,24 @@ export class ChatMessageService { } async findAll({ - userId, + where, page = 1, limit = 20, - search = '', }: { - userId: string; + where: FindOptionsWhere; page?: number; limit?: number; - search?: string; }): Promise { const { find } = await createPagination(this.chatMessageRepository, { page, limit, }); const chatMessages = await find({ - where: { content: search, user: { id: userId } }, + where, + relations: { + user: true, + chatRoom: true, + } }).run(); return new PaginatedChatMessageResponseDto( chatMessages.data, diff --git a/src/chat-message/enums/chat-message-type.enum.ts b/src/chat-message/enums/chat-message-type.enum.ts index d0f64f5..5c85d1c 100644 --- a/src/chat-message/enums/chat-message-type.enum.ts +++ b/src/chat-message/enums/chat-message-type.enum.ts @@ -1,5 +1,5 @@ export enum ChatMessageType { - TEXT = 'TEXT', - CODE = 'CODE', - IMAGE = 'IMAGE', + TEXT = 'text', + CODE = 'code', + IMAGE = 'image', } diff --git a/src/chat-message/guards/create-chat-message.guard.ts b/src/chat-message/guards/create-chat-message.guard.ts index db5542d..609eff2 100644 --- a/src/chat-message/guards/create-chat-message.guard.ts +++ b/src/chat-message/guards/create-chat-message.guard.ts @@ -2,15 +2,8 @@ import { Injectable, CanActivate, ExecutionContext, - NotFoundException, ForbiddenException, - UnauthorizedException, } from '@nestjs/common'; -import { JwtService } from '@nestjs/jwt'; -import { ConfigService } from '@nestjs/config'; -import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; -import { Request } from 'express'; -import { JwtPayloadDto } from 'src/auth/dtos/jwt-payload.dto'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { Role } from 'src/shared/enums'; import { CreateChatMessageDto } from '../dtos'; @@ -20,52 +13,34 @@ import { EnrollmentService } from 'src/enrollment/enrollment.service'; @Injectable() export class CreateChatMessageGuard implements CanActivate { constructor( - private readonly jwtService: JwtService, - private readonly configService: ConfigService, private readonly chatRoomService: ChatRoomService, private readonly enrollmentService: EnrollmentService, ) { } async canActivate(context: ExecutionContext): Promise { const request: AuthenticatedRequest = context.switchToHttp().getRequest(); - const createChatMessageDto = request.body as CreateChatMessageDto; - const token = this.extractTokenFromHeader(request); - if (!token) throw new UnauthorizedException('Unauthorized access'); - try { - await this.jwtService.verifyAsync(token, { - secret: this.configService.get(GLOBAL_CONFIG.JWT_AI_ACCESS_SECRET), - }); - return true; - } catch { } - try { - request.user = await this.jwtService.verifyAsync(token, { - secret: this.configService.get(GLOBAL_CONFIG.JWT_ACCESS_SECRET), - }); - } catch (error) { - throw new UnauthorizedException('Unauthorized access'); - } - if (request.user.role !== Role.STUDENT) - throw new ForbiddenException('Forbidden access'); - const chatRoom = await this.chatRoomService.findOne({ - where: { id: createChatMessageDto.chatRoomId }, - relations: { - chapter: { - module: { - course: true, - }, + if (request.user.role === Role.STUDENT) { + const createChatMessageDto = request.body as CreateChatMessageDto; + const chatRoom = await this.chatRoomService.findOne({ + where: { id: createChatMessageDto.chatRoomId }, + relations: { + chapter: { + module: { + course: true, + }, + } } + }); + const courseId = chatRoom.chapter.module.course.id; + try { + await this.enrollmentService.findOne({ + user: { id: request.user.id }, + course: { id: courseId }, + }); + } catch { + throw new ForbiddenException('You are not enrolled in this course'); } - }); - const courseId = chatRoom.chapter.module.course.id; - await this.enrollmentService.findOne({ - user: { id: request.user.id }, - course: { id: courseId }, - }); + } return true; } - - private extractTokenFromHeader(request: Request): string | undefined { - const [type, token] = request.headers.authorization?.split(' ') ?? []; - return type === 'Bearer' ? token : undefined; - } } diff --git a/src/chat-room/chat-room.controller.ts b/src/chat-room/chat-room.controller.ts index b99624f..9f00ccc 100644 --- a/src/chat-room/chat-room.controller.ts +++ b/src/chat-room/chat-room.controller.ts @@ -33,13 +33,65 @@ import { CreateChatRoomDto } from './dtos/create-chat-room.dto'; import { UpdateChatRoomDto } from './dtos/update-chat-room.dto'; import { ChatRoomOwnershipGuard } from './guards/chat-room-ownership.guard'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { PaginatedChatMessageResponseDto } from 'src/chat-message/dtos'; +import { ChatMessageService } from 'src/chat-message/chat-message.service'; @Controller('chat-room') @Injectable() @ApiTags('Chat Room') @ApiBearerAuth() export class ChatRoomController { - constructor(private readonly chatRoomService: ChatRoomService) { } + constructor( + private readonly chatRoomService: ChatRoomService, + private readonly chatMessageService: ChatMessageService, + ) { } + + @Get(':id/messages') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Get chat room messages', + type: PaginatedChatMessageResponseDto, + }) + @ApiParam({ + name: 'id', + type: String, + required: true, + }) + @Roles(Role.STUDENT) + @UseGuards(ChatRoomOwnershipGuard) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + }) + async findMessages( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @Query() query: PaginateQueryDto, + ) { + return await this.chatMessageService.findAll({ + ...query, + where: { + chatRoom: { id }, + } + }) + } @Get() @ApiResponse({ diff --git a/src/chat-room/chat-room.module.ts b/src/chat-room/chat-room.module.ts index 14a682b..f49f440 100644 --- a/src/chat-room/chat-room.module.ts +++ b/src/chat-room/chat-room.module.ts @@ -1,4 +1,4 @@ -import { Module } from '@nestjs/common'; +import { forwardRef, Module } from '@nestjs/common'; import { DatabaseModule } from 'src/database/database.module'; import { ChatRoomController } from './chat-room.controller'; import { ChatRoomService } from './chat-room.service'; @@ -6,12 +6,14 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { ChatRoom } from './chat-room.entity'; import { chatRoomProviders } from './chat-room.providers'; import { EnrollmentModule } from 'src/enrollment/enrollment.module'; +import { ChatMessageModule } from 'src/chat-message/chat-message.module'; @Module({ imports: [ DatabaseModule, TypeOrmModule.forFeature([ChatRoom]), EnrollmentModule, + forwardRef(() => ChatMessageModule), ], controllers: [ChatRoomController], providers: [...chatRoomProviders, ChatRoomService], diff --git a/src/chat-room/chat-room.service.ts b/src/chat-room/chat-room.service.ts index dda0bec..4a115e3 100644 --- a/src/chat-room/chat-room.service.ts +++ b/src/chat-room/chat-room.service.ts @@ -12,7 +12,6 @@ import { PaginatedChatRoomResponseDto, CreateChatRoomDto, } from './dtos'; -import { Role } from 'src/shared/enums'; @Injectable() export class ChatRoomService { @@ -71,11 +70,9 @@ export class ChatRoomService { } async findOne(options: FindOneOptions): Promise { - try { - return await this.chatRoomRepository.findOne(options); - } catch (error) { - if (error instanceof Error) throw new NotFoundException(error.message); - } + const chatRoom = await this.chatRoomRepository.findOne(options); + if (!chatRoom) throw new NotFoundException('Chat room not found'); + return chatRoom; } async update( diff --git a/src/chat-room/guards/chat-room-ownership.guard.ts b/src/chat-room/guards/chat-room-ownership.guard.ts index 944a739..1e576d6 100644 --- a/src/chat-room/guards/chat-room-ownership.guard.ts +++ b/src/chat-room/guards/chat-room-ownership.guard.ts @@ -14,7 +14,7 @@ import { Role } from 'src/shared/enums'; export class ChatRoomOwnershipGuard implements CanActivate { constructor( private readonly chatRoomService: ChatRoomService, - private readonly enrollmentRepository: EnrollmentService, + private readonly enrollmentService: EnrollmentService, ) { } async canActivate(context: ExecutionContext): Promise { @@ -25,9 +25,16 @@ export class ChatRoomOwnershipGuard implements CanActivate { case Role.STUDENT: const chatRoom = await this.chatRoomService.findOne({ where: { id: chatRoomId }, + relations: { + chapter: { + module: { + course: true, + }, + }, + } }); const course = chatRoom.chapter.module.course; - const enrollment = await this.enrollmentRepository.findOne({ + const enrollment = await this.enrollmentService.findOne({ user: { id: request.user.id }, course: { id: course.id }, }); diff --git a/src/enrollment/enrollment.controller.ts b/src/enrollment/enrollment.controller.ts index f24d2d5..3ae3704 100644 --- a/src/enrollment/enrollment.controller.ts +++ b/src/enrollment/enrollment.controller.ts @@ -1,18 +1,18 @@ import { - BadRequestException, - Body, - Controller, - Delete, - Get, - HttpCode, - HttpStatus, - Injectable, - Param, - ParseUUIDPipe, - Patch, - Post, - Query, - Req, + BadRequestException, + Body, + Controller, + Delete, + Get, + HttpCode, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, } from '@nestjs/common'; import { ApiBearerAuth, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; @@ -21,8 +21,8 @@ import { Role } from 'src/shared/enums'; import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { CreateEnrollmentDto } from './dtos/create-enrollment.dto'; import { - EnrollmentResponseDto, - PaginatedEnrollmentResponseDto, + EnrollmentResponseDto, + PaginatedEnrollmentResponseDto, } from './dtos/enrollment-response.dto'; import { UpdateEnrollmentDto } from './dtos/update-enrollment.dto'; import { EnrollmentService } from './enrollment.service'; @@ -32,91 +32,90 @@ import { EnrollmentService } from './enrollment.service'; @ApiBearerAuth() @Injectable() export class EnrollmentController { - constructor(private readonly enrollmentService: EnrollmentService) {} + constructor(private readonly enrollmentService: EnrollmentService) { } - @Get() - @ApiResponse({ - status: HttpStatus.OK, - type: EnrollmentResponseDto, - description: 'Get all enrollments', - isArray: true, - }) - async findAll( - @Query() query: PaginateQueryDto, - ): Promise { - return this.enrollmentService.findAll(query); - } + @Get() + @ApiResponse({ + status: HttpStatus.OK, + type: EnrollmentResponseDto, + description: 'Get all enrollments', + isArray: true, + }) + async findAll( + @Query() query: PaginateQueryDto, + ): Promise { + return this.enrollmentService.findAll(query); + } - @Get(':id') - @ApiParam({ - name: 'id', - type: String, - description: 'Enrollment ID', - }) - @ApiResponse({ - status: HttpStatus.OK, - type: EnrollmentResponseDto, - description: 'Get enrollment by ID', - }) - async findOne( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - ): Promise { - return await this.enrollmentService.findOne({ id }); - } + @Get(':id') + @ApiParam({ + name: 'id', + type: String, + description: 'Enrollment ID', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: EnrollmentResponseDto, + description: 'Get enrollment by ID', + }) + async findOne( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + return await this.enrollmentService.findOne({ id }); + } - @Post() - @Roles(Role.STUDENT) - @ApiResponse({ - status: HttpStatus.CREATED, - type: EnrollmentResponseDto, - description: 'Create enrollment', - }) - async create( - @Body() createEnrollmentDto: CreateEnrollmentDto, - @Req() req: AuthenticatedRequest, - ): Promise { - return await this.enrollmentService.create(createEnrollmentDto); - } + @Post() + @Roles(Role.STUDENT) + @ApiResponse({ + status: HttpStatus.CREATED, + type: EnrollmentResponseDto, + description: 'Create enrollment', + }) + async create( + @Body() createEnrollmentDto: CreateEnrollmentDto, + ): Promise { + return await this.enrollmentService.create(createEnrollmentDto); + } - @Patch(':id') - @Roles(Role.STUDENT) - @ApiParam({ - name: 'id', - type: String, - description: 'Enrollment ID', - }) - @ApiResponse({ - status: HttpStatus.OK, - type: EnrollmentResponseDto, - description: 'Update enrollment by ID', - }) - async update( - @Param('id', ParseUUIDPipe) id: string, - @Body() updateEnrollmentDto: UpdateEnrollmentDto, - ): Promise { - return await this.enrollmentService.update(id, updateEnrollmentDto); - } + @Patch(':id') + @Roles(Role.STUDENT) + @ApiParam({ + name: 'id', + type: String, + description: 'Enrollment ID', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: EnrollmentResponseDto, + description: 'Update enrollment by ID', + }) + async update( + @Param('id', ParseUUIDPipe) id: string, + @Body() updateEnrollmentDto: UpdateEnrollmentDto, + ): Promise { + return await this.enrollmentService.update(id, updateEnrollmentDto); + } - @Delete(':id') - @Roles(Role.STUDENT) - @ApiParam({ - name: 'id', - type: String, - description: 'Enrollment ID', - }) - @ApiResponse({ - status: HttpStatus.NO_CONTENT, - description: 'Delete enrollment by ID', - }) - @HttpCode(HttpStatus.NO_CONTENT) - async remove(@Param('id', ParseUUIDPipe) id: string): Promise { - return await this.enrollmentService.remove(id); - } + @Delete(':id') + @Roles(Role.STUDENT) + @ApiParam({ + name: 'id', + type: String, + description: 'Enrollment ID', + }) + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Delete enrollment by ID', + }) + @HttpCode(HttpStatus.NO_CONTENT) + async remove(@Param('id', ParseUUIDPipe) id: string): Promise { + return await this.enrollmentService.remove(id); + } } diff --git a/src/enrollment/enrollment.entity.ts b/src/enrollment/enrollment.entity.ts index 4396989..31eb44a 100644 --- a/src/enrollment/enrollment.entity.ts +++ b/src/enrollment/enrollment.entity.ts @@ -18,11 +18,11 @@ export class Enrollment { @PrimaryGeneratedColumn('uuid') id: string; - @ManyToOne(() => User, { onDelete: 'CASCADE' }) + @ManyToOne(() => User, { onDelete: 'CASCADE', eager: true }) @JoinColumn({ name: 'user_id' }) user: User; - @ManyToOne(() => Course, { onDelete: 'CASCADE' }) + @ManyToOne(() => Course, { onDelete: 'CASCADE', eager: true }) @JoinColumn({ name: 'course_id' }) course: Course; diff --git a/src/enrollment/enrollment.service.ts b/src/enrollment/enrollment.service.ts index ddb1bef..038c90a 100644 --- a/src/enrollment/enrollment.service.ts +++ b/src/enrollment/enrollment.service.ts @@ -1,6 +1,7 @@ import { BadRequestException, Injectable, + InternalServerErrorException, NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; @@ -58,10 +59,21 @@ export class EnrollmentService { } async create(createEnrollmentDto: CreateEnrollmentDto): Promise { - const enrollment = this.enrollmentRepository.create(createEnrollmentDto); - await this.enrollmentRepository.save(enrollment); - - return enrollment; + try { + const enrollment = await this.findOne({ + user: { id: createEnrollmentDto.userId }, + course: { id: createEnrollmentDto.courseId }, + }); + if (enrollment) throw new BadRequestException('Enrollment already exists'); + const createdEnrollment = this.enrollmentRepository.create(createEnrollmentDto); + return await this.enrollmentRepository.save({ + ...createdEnrollment, + user: { id: createEnrollmentDto.userId }, + course: { id: createEnrollmentDto.courseId }, + }); + } catch (error) { + throw new InternalServerErrorException(error.message); + } } async update( diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 8a6cb8b..82752a9 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,9 +1,9 @@ import { - BadRequestException, - Inject, - Injectable, - NotFoundException, - OnModuleInit, + BadRequestException, + Inject, + Injectable, + NotFoundException, + OnModuleInit, } from '@nestjs/common'; import { createPagination } from 'src/shared/pagination'; import { FindOneOptions, ILike, Repository, FindOptionsWhere } from 'typeorm'; @@ -17,28 +17,28 @@ import { hash } from 'argon2'; @Injectable() export class UserService implements OnModuleInit { - constructor( - @Inject('UserRepository') - private readonly userRepository: Repository, - private readonly configService: ConfigService, - ) {} + constructor( + @Inject('UserRepository') + private readonly userRepository: Repository, + private readonly configService: ConfigService, + ) { } - async onModuleInit() { - const adminEmail = this.configService.get('ADMIN_EMAIL'); - const adminPassword = this.configService.get('ADMIN_PASSWORD'); - const admin = await this.userRepository.findOne({ - where: { email: adminEmail }, - }); - if (!admin) { - await this.userRepository.save({ - email: adminEmail, - password: await hash(adminPassword), - fullname: 'Admin', - role: Role.ADMIN, - username: 'admin', - }); + async onModuleInit() { + const adminEmail = this.configService.get('ADMIN_EMAIL'); + const adminPassword = this.configService.get('ADMIN_PASSWORD'); + const admin = await this.userRepository.findOne({ + where: { email: adminEmail }, + }); + if (!admin) { + await this.userRepository.save({ + email: adminEmail, + password: await hash(adminPassword), + fullname: 'Admin', + role: Role.ADMIN, + username: 'admin', + }); + } } - } async findAll({ page = 1, @@ -61,17 +61,17 @@ export class UserService implements OnModuleInit { return users; } - async findOne(options: FindOneOptions): Promise { - return await this.userRepository.findOne(options); - } + async findOne(options: FindOneOptions): Promise { + return await this.userRepository.findOne(options); + } - async create(createUserDto: CreateUserDto): Promise { - try { - return await this.userRepository.save(createUserDto); - } catch (error) { - if (error instanceof Error) throw new BadRequestException(error.message); + async create(createUserDto: CreateUserDto): Promise { + try { + return await this.userRepository.save(createUserDto); + } catch (error) { + if (error instanceof Error) throw new BadRequestException(error.message); + } } - } async update( id: string, @@ -85,11 +85,11 @@ export class UserService implements OnModuleInit { } } - async delete(criteria: FindOptionsWhere): Promise { - try { - await this.userRepository.delete(criteria); - } catch (error) { - if (error instanceof Error) throw new NotFoundException('User not found'); + async delete(criteria: FindOptionsWhere): Promise { + try { + await this.userRepository.delete(criteria); + } catch (error) { + if (error instanceof Error) throw new NotFoundException('User not found'); + } } - } } From cfee04742a62e1727b70fd5632cc0614bcc013a2 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Tue, 19 Nov 2024 15:47:32 +0700 Subject: [PATCH 087/155] refactor: remove console.log statements from chat message service, progress controller, and course ownership guard --- src/chat-message/chat-message.service.ts | 1 - src/progress/progress.controller.ts | 2 -- src/shared/guards/course-ownership.guard.ts | 3 --- 3 files changed, 6 deletions(-) diff --git a/src/chat-message/chat-message.service.ts b/src/chat-message/chat-message.service.ts index 03d8b80..9dc6555 100644 --- a/src/chat-message/chat-message.service.ts +++ b/src/chat-message/chat-message.service.ts @@ -25,7 +25,6 @@ export class ChatMessageService { createChatMessageDto: CreateChatMessageDto, ): Promise { try { - console.log(createChatMessageDto.chatRoomId); return await this.chatMessageRepository.save({ ...createChatMessageDto, user: { id: userId }, diff --git a/src/progress/progress.controller.ts b/src/progress/progress.controller.ts index a3802aa..7a5e3a1 100644 --- a/src/progress/progress.controller.ts +++ b/src/progress/progress.controller.ts @@ -66,7 +66,6 @@ export class ProgressController { async findAll( @Query() query: PaginateQueryDto, ): Promise { - console.log(query); return this.progressService.findAll(query); } @@ -93,7 +92,6 @@ export class ProgressController { @Body() createProgressDto: CreateProgressDto, @Req() req: AuthenticatedRequest, ): Promise { - console.log(createProgressDto); return this.progressService.create(createProgressDto); } diff --git a/src/shared/guards/course-ownership.guard.ts b/src/shared/guards/course-ownership.guard.ts index 99b715d..7fbe395 100644 --- a/src/shared/guards/course-ownership.guard.ts +++ b/src/shared/guards/course-ownership.guard.ts @@ -115,9 +115,6 @@ export class CourseOwnershipGuard implements CanActivate { } private validateTeacherAccess(userId: string, course: Course): boolean { - console.log(course.teacher.id); - console.log(course); - console.log(userId); if (course.teacher.id !== userId) { throw new UnauthorizedException('You can only access your own courses'); } From d9647e70aef12b5155881eabf0c3da7ac48dfca9 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Tue, 19 Nov 2024 15:47:46 +0700 Subject: [PATCH 088/155] style: format code for consistency and readability across multiple files --- src/auth/auth.service.ts | 62 +-- src/chapter/chapter.controller.ts | 313 +++++------ src/chapter/chapter.service.ts | 508 +++++++++--------- src/chat-message/chat-message.controller.ts | 187 +++---- src/chat-message/chat-message.entity.ts | 82 +-- src/chat-message/chat-message.module.ts | 20 +- src/chat-message/chat-message.service.ts | 160 +++--- .../dtos/create-chat-message.dto.ts | 78 +-- .../guards/create-chat-message.guard.ts | 66 +-- src/chat-room/chat-room.controller.ts | 381 ++++++------- src/chat-room/chat-room.entity.ts | 100 ++-- src/chat-room/chat-room.service.ts | 161 +++--- .../dtos/paginated-chat-room-response.dto.ts | 116 ++-- .../guards/chat-room-ownership.guard.ts | 110 ++-- src/course/course.service.ts | 340 ++++++------ src/enrollment/enrollment.controller.ts | 194 +++---- src/enrollment/enrollment.service.ts | 148 ++--- src/shared/configs/dotenv.config.ts | 48 +- .../constants/global-config.constant.ts | 36 +- src/user/user.service.ts | 136 ++--- 20 files changed, 1633 insertions(+), 1613 deletions(-) diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index c07b5e4..2108f66 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -1,8 +1,8 @@ import { - Injectable, - NotFoundException, - BadRequestException, - InternalServerErrorException, + Injectable, + NotFoundException, + BadRequestException, + InternalServerErrorException, } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { JwtService } from '@nestjs/jwt'; @@ -19,12 +19,12 @@ import { Role } from 'src/shared/enums'; @Injectable() export class AuthService { - constructor( - private readonly configService: ConfigService, - private readonly jwtService: JwtService, - private readonly userService: UserService, - private readonly userStreakService: UserStreakService, - ) { } + constructor( + private readonly configService: ConfigService, + private readonly jwtService: JwtService, + private readonly userService: UserService, + private readonly userStreakService: UserStreakService, + ) {} async login(loginDto: LoginDto): Promise { const user = await this.userService.findOne({ @@ -83,26 +83,26 @@ export class AuthService { } } - private generateAccessToken(payload: JwtPayloadDto): string { - return this.jwtService.sign(payload, { - secret: this.configService.get(GLOBAL_CONFIG.JWT_ACCESS_SECRET), - expiresIn: this.configService.get( - GLOBAL_CONFIG.JWT_ACCESS_EXPIRATION, - ), - }); - } + private generateAccessToken(payload: JwtPayloadDto): string { + return this.jwtService.sign(payload, { + secret: this.configService.get(GLOBAL_CONFIG.JWT_ACCESS_SECRET), + expiresIn: this.configService.get( + GLOBAL_CONFIG.JWT_ACCESS_EXPIRATION, + ), + }); + } - private generateRefreshToken(): string { - return this.jwtService.sign( - {}, - { - secret: this.configService.get( - GLOBAL_CONFIG.JWT_REFRESH_SECRET, - ), - expiresIn: this.configService.get( - GLOBAL_CONFIG.JWT_REFRESH_EXPIRATION, - ), - }, - ); - } + private generateRefreshToken(): string { + return this.jwtService.sign( + {}, + { + secret: this.configService.get( + GLOBAL_CONFIG.JWT_REFRESH_SECRET, + ), + expiresIn: this.configService.get( + GLOBAL_CONFIG.JWT_REFRESH_EXPIRATION, + ), + }, + ); + } } diff --git a/src/chapter/chapter.controller.ts b/src/chapter/chapter.controller.ts index 536cec5..fb3b1b2 100644 --- a/src/chapter/chapter.controller.ts +++ b/src/chapter/chapter.controller.ts @@ -1,25 +1,25 @@ import { - BadRequestException, - Body, - Controller, - Delete, - Get, - HttpStatus, - Injectable, - Param, - ParseUUIDPipe, - Patch, - Post, - Query, - Req, - UseGuards, + BadRequestException, + Body, + Controller, + Delete, + Get, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, + UseGuards, } from '@nestjs/common'; import { - ApiBearerAuth, - ApiParam, - ApiQuery, - ApiResponse, - ApiTags, + ApiBearerAuth, + ApiParam, + ApiQuery, + ApiResponse, + ApiTags, } from '@nestjs/swagger'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { Roles } from 'src/shared/decorators/role.decorator'; @@ -27,8 +27,8 @@ import { Role } from 'src/shared/enums'; import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { ChapterService } from './chapter.service'; import { - ChapterResponseDto, - PaginatedChapterResponseDto, + ChapterResponseDto, + PaginatedChapterResponseDto, } from './dtos/chapter-response.dto'; import { CreateChapterDto } from './dtos/create-chapter.dto'; import { UpdateChapterDto } from './dtos/update-chapter.dto'; @@ -42,145 +42,148 @@ import { ChatRoomService } from 'src/chat-room/chat-room.service'; @ApiBearerAuth() @Injectable() export class ChapterController { - constructor( - private readonly chapterService: ChapterService, - private readonly courseModuleService: CourseModuleService, - ) { } + constructor( + private readonly chapterService: ChapterService, + private readonly courseModuleService: CourseModuleService, + ) {} - @Get() - @ApiResponse({ - status: HttpStatus.OK, - type: ChapterResponseDto, - description: 'Get all chapters', - isArray: true, - }) - @ApiQuery({ - name: 'page', - type: Number, - required: false, - description: 'Page number', - }) - @ApiQuery({ - name: 'limit', - type: Number, - required: false, - description: 'Items per page', - }) - async findAll( - @Req() request: AuthenticatedRequest, - @Query() query: PaginateQueryDto, - ): Promise { - return this.chapterService.findAll({ - page: query.page, - limit: query.limit, - search: query.search, - userId: request.user.id, - role: request.user.role, - }); - } - - @Get(':id/chat-rooms') - @ApiResponse({ - status: HttpStatus.OK, - type: ChatRoomResponseDto, - description: 'Get all chat rooms for a chapter', - isArray: true, - }) - @ApiParam({ - name: 'id', - type: String, - description: 'Chapter ID', - }) - @Roles(Role.STUDENT) - async getChatRooms( - @Req() request: AuthenticatedRequest, - @Param('id', ParseUUIDPipe) id: string, - ): Promise { - const chatRooms = await this.chapterService.getChatRooms(request.user.id, id); - return chatRooms.map((chatRoom) => new ChatRoomResponseDto(chatRoom)); - } + @Get() + @ApiResponse({ + status: HttpStatus.OK, + type: ChapterResponseDto, + description: 'Get all chapters', + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + async findAll( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return this.chapterService.findAll({ + page: query.page, + limit: query.limit, + search: query.search, + userId: request.user.id, + role: request.user.role, + }); + } - @Get(':id') - @ApiResponse({ - status: HttpStatus.OK, - type: ChapterResponseDto, - description: 'Get a chapter by ID', - }) - @ApiParam({ - name: 'id', - type: String, - description: 'Chapter ID', - }) - async findOne( - @Req() request: AuthenticatedRequest, - @Param('id', ParseUUIDPipe) id: string, - ): Promise { - return this.chapterService.findOne(request.user.id, request.user.role, { - where: { id }, - }); - } + @Get(':id/chat-rooms') + @ApiResponse({ + status: HttpStatus.OK, + type: ChatRoomResponseDto, + description: 'Get all chat rooms for a chapter', + isArray: true, + }) + @ApiParam({ + name: 'id', + type: String, + description: 'Chapter ID', + }) + @Roles(Role.STUDENT) + async getChatRooms( + @Req() request: AuthenticatedRequest, + @Param('id', ParseUUIDPipe) id: string, + ): Promise { + const chatRooms = await this.chapterService.getChatRooms( + request.user.id, + id, + ); + return chatRooms.map((chatRoom) => new ChatRoomResponseDto(chatRoom)); + } - @Post() - @Roles(Role.TEACHER) - @ApiResponse({ - status: HttpStatus.CREATED, - type: ChapterResponseDto, - description: 'Create a chapter', - }) - async create( - @Req() request: AuthenticatedRequest, - @Body() createChapterDto: CreateChapterDto, - ): Promise { - if (createChapterDto.moduleId != null) { - await this.courseModuleService.validateOwnership( - createChapterDto.moduleId, - request.user.id, - ); - } - return this.chapterService.create(createChapterDto); - } + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + type: ChapterResponseDto, + description: 'Get a chapter by ID', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'Chapter ID', + }) + async findOne( + @Req() request: AuthenticatedRequest, + @Param('id', ParseUUIDPipe) id: string, + ): Promise { + return this.chapterService.findOne(request.user.id, request.user.role, { + where: { id }, + }); + } - @Patch(':id') - @CourseOwnership({ adminDraftOnly: true }) - @ApiResponse({ - status: HttpStatus.OK, - type: ChapterResponseDto, - description: 'Update a chapter', - }) - @ApiParam({ - name: 'id', - type: String, - description: 'Chapter ID', - }) - async update( - @Req() request: AuthenticatedRequest, - @Param('id', ParseUUIDPipe) id: string, - @Body() updateChapterDto: UpdateChapterDto, - ): Promise { - if (updateChapterDto.moduleId != null) { - await this.courseModuleService.validateOwnership( - updateChapterDto.moduleId, - request.user.id, - ); - } - return this.chapterService.update(id, updateChapterDto); + @Post() + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.CREATED, + type: ChapterResponseDto, + description: 'Create a chapter', + }) + async create( + @Req() request: AuthenticatedRequest, + @Body() createChapterDto: CreateChapterDto, + ): Promise { + if (createChapterDto.moduleId != null) { + await this.courseModuleService.validateOwnership( + createChapterDto.moduleId, + request.user.id, + ); } + return this.chapterService.create(createChapterDto); + } - @Delete(':id') - @CourseOwnership() - @ApiResponse({ - status: HttpStatus.OK, - type: ChapterResponseDto, - description: 'Delete a chapter', - }) - @ApiParam({ - name: 'id', - type: String, - description: 'Chapter ID', - }) - async remove( - @Param('id', ParseUUIDPipe) id: string, - ): Promise { - return this.chapterService.remove(id); + @Patch(':id') + @CourseOwnership({ adminDraftOnly: true }) + @ApiResponse({ + status: HttpStatus.OK, + type: ChapterResponseDto, + description: 'Update a chapter', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'Chapter ID', + }) + async update( + @Req() request: AuthenticatedRequest, + @Param('id', ParseUUIDPipe) id: string, + @Body() updateChapterDto: UpdateChapterDto, + ): Promise { + if (updateChapterDto.moduleId != null) { + await this.courseModuleService.validateOwnership( + updateChapterDto.moduleId, + request.user.id, + ); } + return this.chapterService.update(id, updateChapterDto); + } + + @Delete(':id') + @CourseOwnership() + @ApiResponse({ + status: HttpStatus.OK, + type: ChapterResponseDto, + description: 'Delete a chapter', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'Chapter ID', + }) + async remove( + @Param('id', ParseUUIDPipe) id: string, + ): Promise { + return this.chapterService.remove(id); + } } diff --git a/src/chapter/chapter.service.ts b/src/chapter/chapter.service.ts index e670b3b..dce34a8 100644 --- a/src/chapter/chapter.service.ts +++ b/src/chapter/chapter.service.ts @@ -1,8 +1,8 @@ import { - BadRequestException, - ForbiddenException, - Injectable, - NotFoundException, + BadRequestException, + ForbiddenException, + Injectable, + NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { createPagination } from 'src/shared/pagination'; @@ -19,279 +19,279 @@ import { ChatRoom } from 'src/chat-room/chat-room.entity'; @Injectable() export class ChapterService { - constructor( - @InjectRepository(Chapter) - private readonly chapterRepository: Repository, - private readonly chatRoomService: ChatRoomService, - private readonly enrollmentService: EnrollmentService, - ) { } - - async findAll({ - page = 1, - limit = 20, - search = '', - userId, - role, - }: { - page?: number; - limit?: number; - search?: string; - userId: string; - role: Role; - }): Promise { - const { find } = await createPagination(this.chapterRepository, { - page, - limit, - }); - - const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; - const whereCondition = this.buildWhereCondition(userId, role, baseSearch); - - const chapters = await find({ - where: whereCondition, - relations: { - module: true, - }, - }).run(); - - return chapters; - } - - async findOne( - userId: string, - role: Role, - options: FindOneOptions, - ): Promise { - const baseWhere = options.where as FindOptionsWhere; - const whereCondition = this.buildWhereCondition(userId, role, baseWhere); - - const chapter = await this.chapterRepository.findOne({ - where: whereCondition, - relations: { - module: true, - }, - }); - - if (!chapter) { - throw new NotFoundException('Chapter not found'); - } - - return chapter; + constructor( + @InjectRepository(Chapter) + private readonly chapterRepository: Repository, + private readonly chatRoomService: ChatRoomService, + private readonly enrollmentService: EnrollmentService, + ) {} + + async findAll({ + page = 1, + limit = 20, + search = '', + userId, + role, + }: { + page?: number; + limit?: number; + search?: string; + userId: string; + role: Role; + }): Promise { + const { find } = await createPagination(this.chapterRepository, { + page, + limit, + }); + + const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; + const whereCondition = this.buildWhereCondition(userId, role, baseSearch); + + const chapters = await find({ + where: whereCondition, + relations: { + module: true, + }, + }).run(); + + return chapters; + } + + async findOne( + userId: string, + role: Role, + options: FindOneOptions, + ): Promise { + const baseWhere = options.where as FindOptionsWhere; + const whereCondition = this.buildWhereCondition(userId, role, baseWhere); + + const chapter = await this.chapterRepository.findOne({ + where: whereCondition, + relations: { + module: true, + }, + }); + + if (!chapter) { + throw new NotFoundException('Chapter not found'); } - async validateAndGetNextOrderIndex(moduleId: string): Promise { - const existingChapter = await this.chapterRepository.find({ - where: { moduleId }, - order: { orderIndex: 'DESC' }, - }); + return chapter; + } - const nextOrderIndex = existingChapter.map((chapter) => chapter.orderIndex); - const hasDuplicates = - new Set(nextOrderIndex).size !== nextOrderIndex.length; + async validateAndGetNextOrderIndex(moduleId: string): Promise { + const existingChapter = await this.chapterRepository.find({ + where: { moduleId }, + order: { orderIndex: 'DESC' }, + }); - if (hasDuplicates) { - throw new BadRequestException('Order index is duplicated'); - } + const nextOrderIndex = existingChapter.map((chapter) => chapter.orderIndex); + const hasDuplicates = + new Set(nextOrderIndex).size !== nextOrderIndex.length; - return nextOrderIndex.length ? nextOrderIndex[0] + 1 : 1; + if (hasDuplicates) { + throw new BadRequestException('Order index is duplicated'); } - async create(createChapterDto: CreateChapterDto): Promise { - let orderIndex = await this.validateAndGetNextOrderIndex( - createChapterDto.moduleId, - ); - - const createdChapter = this.chapterRepository.create({ - ...createChapterDto, - orderIndex: orderIndex, - }); - const savedChapter = await this.chapterRepository.save(createdChapter); - await this.chatRoomService.create({ - title: `${savedChapter.title} Questions`, - type: ChatRoomType.QUESTION, - chapterId: savedChapter.id, - status: ChatRoomStatus.ACTIVE, - }); - await this.chatRoomService.create({ - title: `${savedChapter.title} Discussion`, - type: ChatRoomType.DISCUSSION, - chapterId: savedChapter.id, - status: ChatRoomStatus.ACTIVE, - }); - return savedChapter; + return nextOrderIndex.length ? nextOrderIndex[0] + 1 : 1; + } + + async create(createChapterDto: CreateChapterDto): Promise { + let orderIndex = await this.validateAndGetNextOrderIndex( + createChapterDto.moduleId, + ); + + const createdChapter = this.chapterRepository.create({ + ...createChapterDto, + orderIndex: orderIndex, + }); + const savedChapter = await this.chapterRepository.save(createdChapter); + await this.chatRoomService.create({ + title: `${savedChapter.title} Questions`, + type: ChatRoomType.QUESTION, + chapterId: savedChapter.id, + status: ChatRoomStatus.ACTIVE, + }); + await this.chatRoomService.create({ + title: `${savedChapter.title} Discussion`, + type: ChatRoomType.DISCUSSION, + chapterId: savedChapter.id, + status: ChatRoomStatus.ACTIVE, + }); + return savedChapter; + } + + async reorderModules(moduleId: string): Promise { + const modulesToReorder = await this.chapterRepository.find({ + where: { moduleId }, + order: { orderIndex: 'ASC' }, + }); + + for (let i = 0; i < modulesToReorder.length; i++) { + modulesToReorder[i].orderIndex = i + 1; } - async reorderModules(moduleId: string): Promise { - const modulesToReorder = await this.chapterRepository.find({ - where: { moduleId }, - order: { orderIndex: 'ASC' }, - }); + await this.chapterRepository.save(modulesToReorder); + } - for (let i = 0; i < modulesToReorder.length; i++) { - modulesToReorder[i].orderIndex = i + 1; - } + async update( + id: string, + updateChapterDto: UpdateChapterDto, + ): Promise { + const chapter = await this.chapterRepository.findOne({ where: { id } }); - await this.chapterRepository.save(modulesToReorder); + if (!chapter) { + throw new NotFoundException('Chapter not found'); } - - async update( - id: string, - updateChapterDto: UpdateChapterDto, - ): Promise { - const chapter = await this.chapterRepository.findOne({ where: { id } }); - - if (!chapter) { - throw new NotFoundException('Chapter not found'); - } - if (updateChapterDto.orderIndex != null) { - await this.validateOrderIndex( - chapter.moduleId, - updateChapterDto.orderIndex, - ); - } - if ( - updateChapterDto.orderIndex && - updateChapterDto.orderIndex !== chapter.orderIndex - ) { - const existingChapter = await this.chapterRepository.findOne({ - where: { - moduleId: chapter.moduleId, - orderIndex: updateChapterDto.orderIndex, - }, - }); - - if (existingChapter) { - await this.chapterRepository.update(existingChapter.id, { - orderIndex: chapter.orderIndex, - }); - } - } - - Object.assign(chapter, updateChapterDto); - await this.chapterRepository.save(chapter); - - return chapter; + if (updateChapterDto.orderIndex != null) { + await this.validateOrderIndex( + chapter.moduleId, + updateChapterDto.orderIndex, + ); + } + if ( + updateChapterDto.orderIndex && + updateChapterDto.orderIndex !== chapter.orderIndex + ) { + const existingChapter = await this.chapterRepository.findOne({ + where: { + moduleId: chapter.moduleId, + orderIndex: updateChapterDto.orderIndex, + }, + }); + + if (existingChapter) { + await this.chapterRepository.update(existingChapter.id, { + orderIndex: chapter.orderIndex, + }); + } } - async remove(id: string): Promise { - const chapter = await this.chapterRepository.findOne({ where: { id } }); + Object.assign(chapter, updateChapterDto); + await this.chapterRepository.save(chapter); - if (!chapter) { - throw new BadRequestException('Chapter not found'); - } + return chapter; + } - const result = await this.chapterRepository.remove(chapter); + async remove(id: string): Promise { + const chapter = await this.chapterRepository.findOne({ where: { id } }); - await this.reorderModules(chapter.moduleId); + if (!chapter) { + throw new BadRequestException('Chapter not found'); + } - return result; + const result = await this.chapterRepository.remove(chapter); + + await this.reorderModules(chapter.moduleId); + + return result; + } + + private async validateOrderIndex( + moduleId: string, + orderIndex: number, + ): Promise { + const existingModules = await this.chapterRepository.find({ + where: { moduleId }, + order: { orderIndex: 'ASC' }, + }); + if (existingModules.length === 0) { + if (orderIndex !== 1) { + throw new BadRequestException( + 'Order index should be 1 when there are no modules in the course', + ); + } + return; } + const minIndex = 1; + const maxIndex = existingModules[existingModules.length - 1].orderIndex; - private async validateOrderIndex( - moduleId: string, - orderIndex: number, - ): Promise { - const existingModules = await this.chapterRepository.find({ - where: { moduleId }, - order: { orderIndex: 'ASC' }, - }); - if (existingModules.length === 0) { - if (orderIndex !== 1) { - throw new BadRequestException( - 'Order index should be 1 when there are no modules in the course', - ); - } - return; - } - const minIndex = 1; - const maxIndex = existingModules[existingModules.length - 1].orderIndex; - - if (orderIndex < minIndex || orderIndex > maxIndex) { - throw new BadRequestException( - `Order index must be between ${minIndex} and ${maxIndex}`, - ); - } + if (orderIndex < minIndex || orderIndex > maxIndex) { + throw new BadRequestException( + `Order index must be between ${minIndex} and ${maxIndex}`, + ); } - async validateOwnership(id: string, userId: string): Promise { - const chapter = await this.chapterRepository.findOne({ - where: { id }, - relations: { module: { course: { teacher: true } } }, - }); - if (!chapter) throw new NotFoundException('Chapter not found'); - if (chapter.module.course.teacher.id !== userId) - throw new BadRequestException('You can only access your own courses'); + } + async validateOwnership(id: string, userId: string): Promise { + const chapter = await this.chapterRepository.findOne({ + where: { id }, + relations: { module: { course: { teacher: true } } }, + }); + if (!chapter) throw new NotFoundException('Chapter not found'); + if (chapter.module.course.teacher.id !== userId) + throw new BadRequestException('You can only access your own courses'); + } + + async getChatRooms(userId: string, chapterId: string): Promise { + try { + const chapter = await this.chapterRepository.findOne({ + where: { id: chapterId }, + relations: { module: { course: true } }, + }); + const courseId = chapter.module.course.id; + const enrollments = await this.enrollmentService.findOne({ + user: { id: userId }, + course: { id: courseId }, + }); + if (!enrollments) + throw new ForbiddenException('You are not enrolled in this course'); + return await this.chatRoomService.find({ + where: { + chapter: { id: chapterId }, + status: ChatRoomStatus.ACTIVE, + }, + }); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); } + } + + private buildWhereCondition( + userId: string, + role: Role, + baseCondition: FindOptionsWhere = {}, + ) { + const conditions: Record< + Role, + () => FindOptionsWhere | FindOptionsWhere[] + > = { + [Role.STUDENT]: () => ({ + ...baseCondition, + module: { + course: { + status: CourseStatus.PUBLISHED, + }, + }, + }), + [Role.TEACHER]: () => [ + { + ...baseCondition, + module: { + course: { + status: CourseStatus.PUBLISHED, + }, + }, + }, + { + ...baseCondition, + module: { + course: { + teacher: { + id: userId, + }, + }, + }, + }, + ], + [Role.ADMIN]: () => baseCondition, + }; - async getChatRooms(userId: string, chapterId: string): Promise { - try { - const chapter = await this.chapterRepository.findOne({ - where: { id: chapterId }, - relations: { module: { course: true } }, - }); - const courseId = chapter.module.course.id; - const enrollments = await this.enrollmentService.findOne({ - user: { id: userId }, - course: { id: courseId }, - }) - if (!enrollments) - throw new ForbiddenException('You are not enrolled in this course'); - return await this.chatRoomService.find({ - where: { - chapter: { id: chapterId }, - status: ChatRoomStatus.ACTIVE, - } - }) - } catch (error) { - if (error instanceof Error) throw new NotFoundException(error.message); - } - } + const buildCondition = conditions[role]; - private buildWhereCondition( - userId: string, - role: Role, - baseCondition: FindOptionsWhere = {}, - ) { - const conditions: Record< - Role, - () => FindOptionsWhere | FindOptionsWhere[] - > = { - [Role.STUDENT]: () => ({ - ...baseCondition, - module: { - course: { - status: CourseStatus.PUBLISHED, - }, - }, - }), - [Role.TEACHER]: () => [ - { - ...baseCondition, - module: { - course: { - status: CourseStatus.PUBLISHED, - }, - }, - }, - { - ...baseCondition, - module: { - course: { - teacher: { - id: userId, - }, - }, - }, - }, - ], - [Role.ADMIN]: () => baseCondition, - }; - - const buildCondition = conditions[role]; - - if (!buildCondition) { - throw new BadRequestException('Invalid role'); - } - - return buildCondition(); + if (!buildCondition) { + throw new BadRequestException('Invalid role'); } + + return buildCondition(); + } } diff --git a/src/chat-message/chat-message.controller.ts b/src/chat-message/chat-message.controller.ts index 624ba38..02fd60d 100644 --- a/src/chat-message/chat-message.controller.ts +++ b/src/chat-message/chat-message.controller.ts @@ -1,18 +1,18 @@ import { - Body, - Controller, - Delete, - Get, - HttpCode, - HttpStatus, - Injectable, - Param, - ParseUUIDPipe, - Patch, - Post, - Query, - Req, - UseGuards, + Body, + Controller, + Delete, + Get, + HttpCode, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, + UseGuards, } from '@nestjs/common'; import { ChatMessageService } from './chat-message.service'; import { ApiTags, ApiBearerAuth, ApiResponse } from '@nestjs/swagger'; @@ -27,87 +27,88 @@ import { CreateChatMessageGuard } from './guards/create-chat-message.guard'; @ApiTags('Chat Message') @ApiBearerAuth() export class ChatMessageController { - constructor( - private readonly chatMessageService: ChatMessageService, - ) { } + constructor(private readonly chatMessageService: ChatMessageService) {} - @Get(':id') - @ApiResponse({ - status: HttpStatus.OK, - description: 'Returns a chat message by id', - type: ChatMessageResponseDto, - }) - @Roles(Role.ADMIN) - async findOne( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - ) { - return await this.chatMessageService.findOne({ where: { id } }); - } + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns a chat message by id', + type: ChatMessageResponseDto, + }) + @Roles(Role.ADMIN) + async findOne( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ) { + return await this.chatMessageService.findOne({ where: { id } }); + } - @Post() - @ApiResponse({ - status: HttpStatus.CREATED, - description: 'Creates a chat message', - type: ChatMessageResponseDto, - }) - @HttpCode(HttpStatus.CREATED) - @UseGuards(CreateChatMessageGuard) - @Roles(Role.STUDENT, Role.ADMIN) - async create( - @Body() createChatMessageDto: CreateChatMessageDto, - @Req() request: AuthenticatedRequest, - ) { - const chatMessage = await this.chatMessageService.create( - request.user.id, - createChatMessageDto, - ); - return new ChatMessageResponseDto(chatMessage); - } + @Post() + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Creates a chat message', + type: ChatMessageResponseDto, + }) + @HttpCode(HttpStatus.CREATED) + @UseGuards(CreateChatMessageGuard) + @Roles(Role.STUDENT, Role.ADMIN) + async create( + @Body() createChatMessageDto: CreateChatMessageDto, + @Req() request: AuthenticatedRequest, + ) { + const chatMessage = await this.chatMessageService.create( + request.user.id, + createChatMessageDto, + ); + return new ChatMessageResponseDto(chatMessage); + } - @Patch(':id') - @ApiResponse({ - status: HttpStatus.OK, - description: 'Updates a chat message by id', - type: ChatMessageResponseDto, - }) - async update( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - @Body() updateChatMessageDto: CreateChatMessageDto, - ) { - const chatMessage = await this.chatMessageService.update({ id }, updateChatMessageDto); - return new ChatMessageResponseDto(chatMessage); - } + @Patch(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Updates a chat message by id', + type: ChatMessageResponseDto, + }) + async update( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @Body() updateChatMessageDto: CreateChatMessageDto, + ) { + const chatMessage = await this.chatMessageService.update( + { id }, + updateChatMessageDto, + ); + return new ChatMessageResponseDto(chatMessage); + } - @Delete(':id') - @ApiResponse({ - status: HttpStatus.NO_CONTENT, - description: 'Deletes a chat message by id', - }) - @HttpCode(HttpStatus.NO_CONTENT) - async delete( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - ) { - await this.chatMessageService.delete({ id }); - } + @Delete(':id') + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Deletes a chat message by id', + }) + @HttpCode(HttpStatus.NO_CONTENT) + async delete( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ) { + await this.chatMessageService.delete({ id }); + } } diff --git a/src/chat-message/chat-message.entity.ts b/src/chat-message/chat-message.entity.ts index 95efca6..8fb4c85 100644 --- a/src/chat-message/chat-message.entity.ts +++ b/src/chat-message/chat-message.entity.ts @@ -1,10 +1,10 @@ import { - Entity, - PrimaryGeneratedColumn, - Column, - OneToOne, - ManyToOne, - JoinColumn, + Entity, + PrimaryGeneratedColumn, + Column, + OneToOne, + ManyToOne, + JoinColumn, } from 'typeorm'; import { ChatMessageType } from './enums/chat-message-type.enum'; import { ChatRoom } from 'src/chat-room/chat-room.entity'; @@ -12,45 +12,45 @@ import { User } from 'src/user/user.entity'; @Entity() export class ChatMessage { - @PrimaryGeneratedColumn('uuid') - id: string; + @PrimaryGeneratedColumn('uuid') + id: string; - @Column({ - nullable: false, - }) - content: string; + @Column({ + nullable: false, + }) + content: string; - @OneToOne(() => ChatMessage, { - nullable: true, - onDelete: 'CASCADE', - }) - @JoinColumn({ name: 'reply' }) - reply?: ChatMessage; + @OneToOne(() => ChatMessage, { + nullable: true, + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'reply' }) + reply?: ChatMessage; - @Column({ - nullable: false, - default: false, - }) - isEdited: boolean; + @Column({ + nullable: false, + default: false, + }) + isEdited: boolean; - @Column({ - type: 'enum', - enum: ChatMessageType, - nullable: false, - }) - type: ChatMessageType; + @Column({ + type: 'enum', + enum: ChatMessageType, + nullable: false, + }) + type: ChatMessageType; - @ManyToOne(() => ChatRoom, (chatRoom) => chatRoom.chatMessages, { - nullable: false, - onDelete: 'CASCADE', - }) - @JoinColumn({ name: 'chat_room_id' }) - chatRoom: ChatRoom; + @ManyToOne(() => ChatRoom, (chatRoom) => chatRoom.chatMessages, { + nullable: false, + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'chat_room_id' }) + chatRoom: ChatRoom; - @ManyToOne(() => User, { - nullable: false, - onDelete: 'CASCADE', - eager: true, - }) - user: User; + @ManyToOne(() => User, { + nullable: false, + onDelete: 'CASCADE', + eager: true, + }) + user: User; } diff --git a/src/chat-message/chat-message.module.ts b/src/chat-message/chat-message.module.ts index 4af7ce2..60115be 100644 --- a/src/chat-message/chat-message.module.ts +++ b/src/chat-message/chat-message.module.ts @@ -9,14 +9,14 @@ import { ChatRoomModule } from 'src/chat-room/chat-room.module'; import { EnrollmentModule } from 'src/enrollment/enrollment.module'; @Module({ - imports: [ - DatabaseModule, - TypeOrmModule.forFeature([ChatMessage]), - forwardRef(() => ChatRoomModule), - EnrollmentModule, - ], - controllers: [ChatMessageController], - providers: [...chatMessageProviders, ChatMessageService], - exports: [ChatMessageService], + imports: [ + DatabaseModule, + TypeOrmModule.forFeature([ChatMessage]), + forwardRef(() => ChatRoomModule), + EnrollmentModule, + ], + controllers: [ChatMessageController], + providers: [...chatMessageProviders, ChatMessageService], + exports: [ChatMessageService], }) -export class ChatMessageModule { } +export class ChatMessageModule {} diff --git a/src/chat-message/chat-message.service.ts b/src/chat-message/chat-message.service.ts index 9dc6555..b84a150 100644 --- a/src/chat-message/chat-message.service.ts +++ b/src/chat-message/chat-message.service.ts @@ -1,100 +1,100 @@ import { - Injectable, - Inject, - InternalServerErrorException, - NotFoundException, + Injectable, + Inject, + InternalServerErrorException, + NotFoundException, } from '@nestjs/common'; import { Repository, FindOptionsWhere, FindOneOptions } from 'typeorm'; import { ChatMessage } from './chat-message.entity'; import { createPagination } from 'src/shared/pagination'; import { - UpdateChatMessageDto, - CreateChatMessageDto, - PaginatedChatMessageResponseDto, + UpdateChatMessageDto, + CreateChatMessageDto, + PaginatedChatMessageResponseDto, } from './dtos'; @Injectable() export class ChatMessageService { - constructor( - @Inject('ChatMessageRepository') - private readonly chatMessageRepository: Repository, - ) { } + constructor( + @Inject('ChatMessageRepository') + private readonly chatMessageRepository: Repository, + ) {} - async create( - userId: string, - createChatMessageDto: CreateChatMessageDto, - ): Promise { - try { - return await this.chatMessageRepository.save({ - ...createChatMessageDto, - user: { id: userId }, - chatRoom: { id: createChatMessageDto.chatRoomId }, - reply: createChatMessageDto.replyId - ? { id: createChatMessageDto.replyId } - : null, - }); - } catch (error) { - if (error instanceof Error) - throw new InternalServerErrorException(error.message); - } + async create( + userId: string, + createChatMessageDto: CreateChatMessageDto, + ): Promise { + try { + return await this.chatMessageRepository.save({ + ...createChatMessageDto, + user: { id: userId }, + chatRoom: { id: createChatMessageDto.chatRoomId }, + reply: createChatMessageDto.replyId + ? { id: createChatMessageDto.replyId } + : null, + }); + } catch (error) { + if (error instanceof Error) + throw new InternalServerErrorException(error.message); } + } - async findAll({ - where, - page = 1, - limit = 20, - }: { - where: FindOptionsWhere; - page?: number; - limit?: number; - }): Promise { - const { find } = await createPagination(this.chatMessageRepository, { - page, - limit, - }); - const chatMessages = await find({ - where, - relations: { - user: true, - chatRoom: true, - } - }).run(); - return new PaginatedChatMessageResponseDto( - chatMessages.data, - chatMessages.meta.total, - chatMessages.meta.pageSize, - chatMessages.meta.currentPage, - ); - } + async findAll({ + where, + page = 1, + limit = 20, + }: { + where: FindOptionsWhere; + page?: number; + limit?: number; + }): Promise { + const { find } = await createPagination(this.chatMessageRepository, { + page, + limit, + }); + const chatMessages = await find({ + where, + relations: { + user: true, + chatRoom: true, + }, + }).run(); + return new PaginatedChatMessageResponseDto( + chatMessages.data, + chatMessages.meta.total, + chatMessages.meta.pageSize, + chatMessages.meta.currentPage, + ); + } - async findOne(options: FindOneOptions): Promise { - try { - return await this.chatMessageRepository.findOne(options); - } catch (error) { - if (error instanceof Error) throw new NotFoundException(error.message); - } + async findOne(options: FindOneOptions): Promise { + try { + return await this.chatMessageRepository.findOne(options); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); } + } - async update( - criteria: FindOptionsWhere, - updateChatMessageDto: UpdateChatMessageDto, - ): Promise { - try { - await this.chatMessageRepository.update(criteria, { - ...updateChatMessageDto, - isEdited: true, - }); - return await this.findOne({ where: criteria }); - } catch (error) { - if (error instanceof Error) throw new NotFoundException(error.message); - } + async update( + criteria: FindOptionsWhere, + updateChatMessageDto: UpdateChatMessageDto, + ): Promise { + try { + await this.chatMessageRepository.update(criteria, { + ...updateChatMessageDto, + isEdited: true, + }); + return await this.findOne({ where: criteria }); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); } + } - async delete(criteria: FindOptionsWhere): Promise { - try { - await this.chatMessageRepository.delete(criteria); - } catch (error) { - if (error instanceof Error) throw new NotFoundException(error.message); - } + async delete(criteria: FindOptionsWhere): Promise { + try { + await this.chatMessageRepository.delete(criteria); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); } + } } diff --git a/src/chat-message/dtos/create-chat-message.dto.ts b/src/chat-message/dtos/create-chat-message.dto.ts index 3dc3a2b..3447038 100644 --- a/src/chat-message/dtos/create-chat-message.dto.ts +++ b/src/chat-message/dtos/create-chat-message.dto.ts @@ -1,49 +1,49 @@ import { - IsString, - IsNotEmpty, - IsOptional, - IsUUID, - IsEnum, + IsString, + IsNotEmpty, + IsOptional, + IsUUID, + IsEnum, } from 'class-validator'; import { ChatMessageType } from '../enums/chat-message-type.enum'; import { ApiProperty } from '@nestjs/swagger'; export class CreateChatMessageDto { - @IsString() - @IsNotEmpty() - @ApiProperty({ - description: 'ChatMessage content', - type: String, - example: 'Hello World!', - }) - content: string; + @IsString() + @IsNotEmpty() + @ApiProperty({ + description: 'ChatMessage content', + type: String, + example: 'Hello World!', + }) + content: string; - @IsOptional() - @IsUUID('4') - @ApiProperty({ - description: 'ChatMessage reply ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - required: false, - }) - replyId?: string; + @IsOptional() + @IsUUID('4') + @ApiProperty({ + description: 'ChatMessage reply ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + required: false, + }) + replyId?: string; - @IsEnum(ChatMessageType) - @IsNotEmpty() - @ApiProperty({ - description: 'ChatMessage type', - type: String, - example: ChatMessageType.TEXT, - enum: ChatMessageType, - }) - type: ChatMessageType; + @IsEnum(ChatMessageType) + @IsNotEmpty() + @ApiProperty({ + description: 'ChatMessage type', + type: String, + example: ChatMessageType.TEXT, + enum: ChatMessageType, + }) + type: ChatMessageType; - @IsNotEmpty() - @IsUUID('4') - @ApiProperty({ - description: 'ChatRoom ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }) - chatRoomId: string; + @IsNotEmpty() + @IsUUID('4') + @ApiProperty({ + description: 'ChatRoom ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + chatRoomId: string; } diff --git a/src/chat-message/guards/create-chat-message.guard.ts b/src/chat-message/guards/create-chat-message.guard.ts index 609eff2..c730670 100644 --- a/src/chat-message/guards/create-chat-message.guard.ts +++ b/src/chat-message/guards/create-chat-message.guard.ts @@ -1,8 +1,8 @@ import { - Injectable, - CanActivate, - ExecutionContext, - ForbiddenException, + Injectable, + CanActivate, + ExecutionContext, + ForbiddenException, } from '@nestjs/common'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { Role } from 'src/shared/enums'; @@ -12,35 +12,35 @@ import { EnrollmentService } from 'src/enrollment/enrollment.service'; @Injectable() export class CreateChatMessageGuard implements CanActivate { - constructor( - private readonly chatRoomService: ChatRoomService, - private readonly enrollmentService: EnrollmentService, - ) { } + constructor( + private readonly chatRoomService: ChatRoomService, + private readonly enrollmentService: EnrollmentService, + ) {} - async canActivate(context: ExecutionContext): Promise { - const request: AuthenticatedRequest = context.switchToHttp().getRequest(); - if (request.user.role === Role.STUDENT) { - const createChatMessageDto = request.body as CreateChatMessageDto; - const chatRoom = await this.chatRoomService.findOne({ - where: { id: createChatMessageDto.chatRoomId }, - relations: { - chapter: { - module: { - course: true, - }, - } - } - }); - const courseId = chatRoom.chapter.module.course.id; - try { - await this.enrollmentService.findOne({ - user: { id: request.user.id }, - course: { id: courseId }, - }); - } catch { - throw new ForbiddenException('You are not enrolled in this course'); - } - } - return true; + async canActivate(context: ExecutionContext): Promise { + const request: AuthenticatedRequest = context.switchToHttp().getRequest(); + if (request.user.role === Role.STUDENT) { + const createChatMessageDto = request.body as CreateChatMessageDto; + const chatRoom = await this.chatRoomService.findOne({ + where: { id: createChatMessageDto.chatRoomId }, + relations: { + chapter: { + module: { + course: true, + }, + }, + }, + }); + const courseId = chatRoom.chapter.module.course.id; + try { + await this.enrollmentService.findOne({ + user: { id: request.user.id }, + course: { id: courseId }, + }); + } catch { + throw new ForbiddenException('You are not enrolled in this course'); + } } + return true; + } } diff --git a/src/chat-room/chat-room.controller.ts b/src/chat-room/chat-room.controller.ts index 9f00ccc..9e63abc 100644 --- a/src/chat-room/chat-room.controller.ts +++ b/src/chat-room/chat-room.controller.ts @@ -1,31 +1,31 @@ import { - Controller, - Injectable, - Query, - HttpStatus, - Param, - ParseUUIDPipe, - Post, - HttpCode, - Body, - Patch, - Delete, - UseGuards, - Req, + Controller, + Injectable, + Query, + HttpStatus, + Param, + ParseUUIDPipe, + Post, + HttpCode, + Body, + Patch, + Delete, + UseGuards, + Req, } from '@nestjs/common'; import { Get } from '@nestjs/common'; import { ChatRoomService } from './chat-room.service'; import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { - ApiTags, - ApiBearerAuth, - ApiResponse, - ApiQuery, - ApiParam, + ApiTags, + ApiBearerAuth, + ApiResponse, + ApiQuery, + ApiParam, } from '@nestjs/swagger'; import { - ChatRoomResponseDto, - PaginatedChatRoomResponseDto, + ChatRoomResponseDto, + PaginatedChatRoomResponseDto, } from './dtos/paginated-chat-room-response.dto'; import { Roles } from 'src/shared/decorators/role.decorator'; import { Role } from 'src/shared/enums'; @@ -41,179 +41,182 @@ import { ChatMessageService } from 'src/chat-message/chat-message.service'; @ApiTags('Chat Room') @ApiBearerAuth() export class ChatRoomController { - constructor( - private readonly chatRoomService: ChatRoomService, - private readonly chatMessageService: ChatMessageService, - ) { } + constructor( + private readonly chatRoomService: ChatRoomService, + private readonly chatMessageService: ChatMessageService, + ) {} - @Get(':id/messages') - @ApiResponse({ - status: HttpStatus.OK, - description: 'Get chat room messages', - type: PaginatedChatMessageResponseDto, - }) - @ApiParam({ - name: 'id', - type: String, - required: true, - }) - @Roles(Role.STUDENT) - @UseGuards(ChatRoomOwnershipGuard) - @ApiQuery({ - name: 'page', - type: Number, - required: false, - }) - @ApiQuery({ - name: 'limit', - type: Number, - required: false, - }) - @ApiQuery({ - name: 'search', - type: String, - required: false, - }) - async findMessages( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - @Query() query: PaginateQueryDto, - ) { - return await this.chatMessageService.findAll({ - ...query, - where: { - chatRoom: { id }, - } - }) - } + @Get(':id/messages') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Get chat room messages', + type: PaginatedChatMessageResponseDto, + }) + @ApiParam({ + name: 'id', + type: String, + required: true, + }) + @Roles(Role.STUDENT) + @UseGuards(ChatRoomOwnershipGuard) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + }) + async findMessages( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @Query() query: PaginateQueryDto, + ) { + return await this.chatMessageService.findAll({ + ...query, + where: { + chatRoom: { id }, + }, + }); + } - @Get() - @ApiResponse({ - status: HttpStatus.OK, - description: 'Get all chat rooms', - type: PaginatedChatRoomResponseDto, - }) - @ApiQuery({ - name: 'page', - type: Number, - required: false, - }) - @ApiQuery({ - name: 'limit', - type: Number, - required: false, - }) - @ApiQuery({ - name: 'search', - type: String, - required: false, - }) - @Roles(Role.TEACHER) - async findAll( - @Query() query: PaginateQueryDto, - @Req() request: AuthenticatedRequest, - ) { - return await this.chatRoomService.findAll({ userId: request.user.id, ...query }); - } + @Get() + @ApiResponse({ + status: HttpStatus.OK, + description: 'Get all chat rooms', + type: PaginatedChatRoomResponseDto, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + }) + @Roles(Role.TEACHER) + async findAll( + @Query() query: PaginateQueryDto, + @Req() request: AuthenticatedRequest, + ) { + return await this.chatRoomService.findAll({ + userId: request.user.id, + ...query, + }); + } - @Get(':id') - @ApiResponse({ - status: HttpStatus.OK, - description: 'Get chat room by id', - type: ChatRoomResponseDto, - }) - @ApiParam({ - name: 'id', - type: String, - required: true, - }) - @Roles(Role.TEACHER, Role.STUDENT) - @UseGuards(ChatRoomOwnershipGuard) - async findById( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - ) { - return new ChatRoomResponseDto( - await this.chatRoomService.findOne({ where: { id } }), - ); - } + @Get(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Get chat room by id', + type: ChatRoomResponseDto, + }) + @ApiParam({ + name: 'id', + type: String, + required: true, + }) + @Roles(Role.TEACHER, Role.STUDENT) + @UseGuards(ChatRoomOwnershipGuard) + async findById( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ) { + return new ChatRoomResponseDto( + await this.chatRoomService.findOne({ where: { id } }), + ); + } - @Post() - @ApiResponse({ - status: HttpStatus.CREATED, - description: 'Create chat room', - type: ChatRoomResponseDto, - }) - @Roles(Role.ADMIN) - @HttpCode(HttpStatus.CREATED) - async create(@Body() createChatRoomDto: CreateChatRoomDto) { - return new ChatRoomResponseDto( - await this.chatRoomService.create(createChatRoomDto), - ); - } + @Post() + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Create chat room', + type: ChatRoomResponseDto, + }) + @Roles(Role.ADMIN) + @HttpCode(HttpStatus.CREATED) + async create(@Body() createChatRoomDto: CreateChatRoomDto) { + return new ChatRoomResponseDto( + await this.chatRoomService.create(createChatRoomDto), + ); + } - @Patch(':id') - @ApiResponse({ - status: HttpStatus.OK, - description: 'Update chat room', - type: ChatRoomResponseDto, - }) - @ApiParam({ - name: 'id', - type: String, - required: true, - }) - @Roles(Role.ADMIN) - async update( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - @Body() updateChatRoomDto: UpdateChatRoomDto, - ) { - return new ChatRoomResponseDto( - await this.chatRoomService.update({ id }, updateChatRoomDto), - ); - } + @Patch(':id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Update chat room', + type: ChatRoomResponseDto, + }) + @ApiParam({ + name: 'id', + type: String, + required: true, + }) + @Roles(Role.ADMIN) + async update( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @Body() updateChatRoomDto: UpdateChatRoomDto, + ) { + return new ChatRoomResponseDto( + await this.chatRoomService.update({ id }, updateChatRoomDto), + ); + } - @Delete(':id') - @ApiResponse({ - status: HttpStatus.NO_CONTENT, - description: 'Delete chat room', - }) - @ApiParam({ - name: 'id', - type: String, - required: true, - }) - @Roles(Role.ADMIN) - @HttpCode(HttpStatus.NO_CONTENT) - async delete( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - ) { - await this.chatRoomService.delete({ id }); - } + @Delete(':id') + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Delete chat room', + }) + @ApiParam({ + name: 'id', + type: String, + required: true, + }) + @Roles(Role.ADMIN) + @HttpCode(HttpStatus.NO_CONTENT) + async delete( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ) { + await this.chatRoomService.delete({ id }); + } } diff --git a/src/chat-room/chat-room.entity.ts b/src/chat-room/chat-room.entity.ts index d05595c..3fcb3e5 100644 --- a/src/chat-room/chat-room.entity.ts +++ b/src/chat-room/chat-room.entity.ts @@ -1,12 +1,12 @@ import { - Entity, - PrimaryGeneratedColumn, - Column, - CreateDateColumn, - UpdateDateColumn, - JoinColumn, - ManyToOne, - OneToMany, + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + JoinColumn, + ManyToOne, + OneToMany, } from 'typeorm'; import { Chapter } from 'src/chapter/chapter.entity'; import { ChatRoomType, ChatRoomStatus } from './enums'; @@ -14,54 +14,54 @@ import { ChatMessage } from 'src/chat-message/chat-message.entity'; @Entity() export class ChatRoom { - @PrimaryGeneratedColumn('uuid') - id: string; + @PrimaryGeneratedColumn('uuid') + id: string; - @ManyToOne(() => Chapter, (chapter) => chapter.chatRooms, { - onDelete: 'CASCADE', - nullable: false, - eager: true, - }) - @JoinColumn({ name: 'chapter_id' }) - chapter: Chapter; + @ManyToOne(() => Chapter, (chapter) => chapter.chatRooms, { + onDelete: 'CASCADE', + nullable: false, + eager: true, + }) + @JoinColumn({ name: 'chapter_id' }) + chapter: Chapter; - @Column({ - nullable: false, - }) - title: string; + @Column({ + nullable: false, + }) + title: string; - @Column({ - nullable: false, - type: 'enum', - enum: ChatRoomType, - default: ChatRoomType.QUESTION, - }) - type: ChatRoomType; + @Column({ + nullable: false, + type: 'enum', + enum: ChatRoomType, + default: ChatRoomType.QUESTION, + }) + type: ChatRoomType; - @Column({ - nullable: false, - type: 'enum', - enum: ChatRoomStatus, - default: ChatRoomStatus.ACTIVE, - }) - status: ChatRoomStatus; + @Column({ + nullable: false, + type: 'enum', + enum: ChatRoomStatus, + default: ChatRoomStatus.ACTIVE, + }) + status: ChatRoomStatus; - @CreateDateColumn({ - type: 'timestamp', - }) - createdAt: Date; + @CreateDateColumn({ + type: 'timestamp', + }) + createdAt: Date; - @Column({ - default: 0, - type: 'int', - }) - participantCount: number; + @Column({ + default: 0, + type: 'int', + }) + participantCount: number; - @UpdateDateColumn({ - type: 'timestamp', - }) - updatedAt: Date; + @UpdateDateColumn({ + type: 'timestamp', + }) + updatedAt: Date; - @OneToMany(() => ChatMessage, (chatMessage) => chatMessage.chatRoom) - chatMessages: ChatMessage[]; + @OneToMany(() => ChatMessage, (chatMessage) => chatMessage.chatRoom) + chatMessages: ChatMessage[]; } diff --git a/src/chat-room/chat-room.service.ts b/src/chat-room/chat-room.service.ts index 4a115e3..882d071 100644 --- a/src/chat-room/chat-room.service.ts +++ b/src/chat-room/chat-room.service.ts @@ -1,97 +1,104 @@ import { - Injectable, - Inject, - InternalServerErrorException, - NotFoundException, + Injectable, + Inject, + InternalServerErrorException, + NotFoundException, } from '@nestjs/common'; -import { Repository, FindOneOptions, FindOptionsWhere, ILike, FindManyOptions } from 'typeorm'; +import { + Repository, + FindOneOptions, + FindOptionsWhere, + ILike, + FindManyOptions, +} from 'typeorm'; import { ChatRoom } from './chat-room.entity'; import { createPagination } from 'src/shared/pagination'; import { - UpdateChatRoomDto, - PaginatedChatRoomResponseDto, - CreateChatRoomDto, + UpdateChatRoomDto, + PaginatedChatRoomResponseDto, + CreateChatRoomDto, } from './dtos'; @Injectable() export class ChatRoomService { - constructor( - @Inject('ChatRoomRepository') - private readonly chatRoomRepository: Repository, - ) { } + constructor( + @Inject('ChatRoomRepository') + private readonly chatRoomRepository: Repository, + ) {} - async create(createChatRoomDto: CreateChatRoomDto): Promise { - try { - return await this.chatRoomRepository.save({ - ...createChatRoomDto, - chapter: { id: createChatRoomDto.chapterId }, - }); - } catch (error) { - if (error instanceof Error) - throw new InternalServerErrorException(error.message); - } + async create(createChatRoomDto: CreateChatRoomDto): Promise { + try { + return await this.chatRoomRepository.save({ + ...createChatRoomDto, + chapter: { id: createChatRoomDto.chapterId }, + }); + } catch (error) { + if (error instanceof Error) + throw new InternalServerErrorException(error.message); } + } - async find(criteria: FindManyOptions): Promise { - try { - return await this.chatRoomRepository.find(criteria); - } catch (error) { - if (error instanceof Error) throw new NotFoundException("Chat room not found"); - } + async find(criteria: FindManyOptions): Promise { + try { + return await this.chatRoomRepository.find(criteria); + } catch (error) { + if (error instanceof Error) + throw new NotFoundException('Chat room not found'); } + } - async findAll({ - userId, - page = 1, - limit = 20, - search = '', - }: { - userId: string; - page?: number; - limit?: number; - search?: string; - }): Promise { - const { find } = await createPagination(this.chatRoomRepository, { - page, - limit, - }); - const chatRooms = await find({ - where: { - title: ILike(`%${search}%`), - chapter: { module: { course: { teacher: { id: userId } } } } - }, - }).run(); - return new PaginatedChatRoomResponseDto( - chatRooms.data, - chatRooms.meta.total, - chatRooms.meta.pageSize, - chatRooms.meta.currentPage, - ); - } + async findAll({ + userId, + page = 1, + limit = 20, + search = '', + }: { + userId: string; + page?: number; + limit?: number; + search?: string; + }): Promise { + const { find } = await createPagination(this.chatRoomRepository, { + page, + limit, + }); + const chatRooms = await find({ + where: { + title: ILike(`%${search}%`), + chapter: { module: { course: { teacher: { id: userId } } } }, + }, + }).run(); + return new PaginatedChatRoomResponseDto( + chatRooms.data, + chatRooms.meta.total, + chatRooms.meta.pageSize, + chatRooms.meta.currentPage, + ); + } - async findOne(options: FindOneOptions): Promise { - const chatRoom = await this.chatRoomRepository.findOne(options); - if (!chatRoom) throw new NotFoundException('Chat room not found'); - return chatRoom; - } + async findOne(options: FindOneOptions): Promise { + const chatRoom = await this.chatRoomRepository.findOne(options); + if (!chatRoom) throw new NotFoundException('Chat room not found'); + return chatRoom; + } - async update( - criteria: FindOptionsWhere, - updateChatRoomDto: UpdateChatRoomDto, - ): Promise { - try { - await this.chatRoomRepository.update(criteria, updateChatRoomDto); - return await this.findOne({ where: criteria }); - } catch (error) { - if (error instanceof Error) throw new NotFoundException(error.message); - } + async update( + criteria: FindOptionsWhere, + updateChatRoomDto: UpdateChatRoomDto, + ): Promise { + try { + await this.chatRoomRepository.update(criteria, updateChatRoomDto); + return await this.findOne({ where: criteria }); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); } + } - async delete(criteria: FindOptionsWhere): Promise { - try { - await this.chatRoomRepository.delete(criteria); - } catch (error) { - if (error instanceof Error) throw new NotFoundException(error.message); - } + async delete(criteria: FindOptionsWhere): Promise { + try { + await this.chatRoomRepository.delete(criteria); + } catch (error) { + if (error instanceof Error) throw new NotFoundException(error.message); } + } } diff --git a/src/chat-room/dtos/paginated-chat-room-response.dto.ts b/src/chat-room/dtos/paginated-chat-room-response.dto.ts index db94bd7..544d43b 100644 --- a/src/chat-room/dtos/paginated-chat-room-response.dto.ts +++ b/src/chat-room/dtos/paginated-chat-room-response.dto.ts @@ -5,72 +5,72 @@ import { ChatRoomType, ChatRoomStatus } from '../enums'; import { ChapterResponseDto } from 'src/chapter/dtos/chapter-response.dto'; export class ChatRoomResponseDto { - @ApiProperty({ - description: 'ChatRoom ID', - type: String, - example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', - }) - id: string; + @ApiProperty({ + description: 'ChatRoom ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + id: string; - @ApiProperty({ - description: 'Chapter', - type: String, - example: 'Chat Room 1', - }) - chapter: ChapterResponseDto; + @ApiProperty({ + description: 'Chapter', + type: String, + example: 'Chat Room 1', + }) + chapter: ChapterResponseDto; - @ApiProperty({ - description: 'ChatRoom title', - type: String, - example: 'Chat Room 1', - }) - title: string; + @ApiProperty({ + description: 'ChatRoom title', + type: String, + example: 'Chat Room 1', + }) + title: string; - @ApiProperty({ - description: 'ChatRoom status', - type: String, - example: ChatRoomStatus.ACTIVE, - enum: ChatRoomStatus, - }) - status: ChatRoomStatus; + @ApiProperty({ + description: 'ChatRoom status', + type: String, + example: ChatRoomStatus.ACTIVE, + enum: ChatRoomStatus, + }) + status: ChatRoomStatus; - @ApiProperty({ - description: 'ChatRoom type', - type: String, - example: ChatRoomType.QUESTION, - enum: ChatRoomType, - }) - type: ChatRoomType; + @ApiProperty({ + description: 'ChatRoom type', + type: String, + example: ChatRoomType.QUESTION, + enum: ChatRoomType, + }) + type: ChatRoomType; - @ApiProperty({ - description: 'ChatRoom participant count', - type: Number, - example: 5, - }) - paticipantCount: number; + @ApiProperty({ + description: 'ChatRoom participant count', + type: Number, + example: 5, + }) + paticipantCount: number; - constructor(chatRoom: ChatRoom) { - this.id = chatRoom.id; - this.title = chatRoom.title; - this.status = chatRoom.status; - this.type = chatRoom.type; - this.paticipantCount = chatRoom.participantCount; - this.chapter = new ChapterResponseDto(chatRoom.chapter); - } + constructor(chatRoom: ChatRoom) { + this.id = chatRoom.id; + this.title = chatRoom.title; + this.status = chatRoom.status; + this.type = chatRoom.type; + this.paticipantCount = chatRoom.participantCount; + this.chapter = new ChapterResponseDto(chatRoom.chapter); + } } export class PaginatedChatRoomResponseDto extends PaginatedResponse( - ChatRoomResponseDto, + ChatRoomResponseDto, ) { - constructor( - chatRooms: ChatRoom[], - total: number, - pageSize: number, - currentPage: number, - ) { - const chatRoomDtos = chatRooms.map( - (chatRoom) => new ChatRoomResponseDto(chatRoom), - ); - super(chatRoomDtos, total, pageSize, currentPage); - } + constructor( + chatRooms: ChatRoom[], + total: number, + pageSize: number, + currentPage: number, + ) { + const chatRoomDtos = chatRooms.map( + (chatRoom) => new ChatRoomResponseDto(chatRoom), + ); + super(chatRoomDtos, total, pageSize, currentPage); + } } diff --git a/src/chat-room/guards/chat-room-ownership.guard.ts b/src/chat-room/guards/chat-room-ownership.guard.ts index 1e576d6..d71b9ed 100644 --- a/src/chat-room/guards/chat-room-ownership.guard.ts +++ b/src/chat-room/guards/chat-room-ownership.guard.ts @@ -1,9 +1,9 @@ import { - Injectable, - CanActivate, - ExecutionContext, - NotFoundException, - ForbiddenException, + Injectable, + CanActivate, + ExecutionContext, + NotFoundException, + ForbiddenException, } from '@nestjs/common'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { ChatRoomService } from '../chat-room.service'; @@ -12,54 +12,58 @@ import { Role } from 'src/shared/enums'; @Injectable() export class ChatRoomOwnershipGuard implements CanActivate { - constructor( - private readonly chatRoomService: ChatRoomService, - private readonly enrollmentService: EnrollmentService, - ) { } + constructor( + private readonly chatRoomService: ChatRoomService, + private readonly enrollmentService: EnrollmentService, + ) {} - async canActivate(context: ExecutionContext): Promise { - const request: AuthenticatedRequest = context.switchToHttp().getRequest(); - const userId = request.user.id; - const chatRoomId = request.params.id; - switch (request.user.role) { - case Role.STUDENT: - const chatRoom = await this.chatRoomService.findOne({ - where: { id: chatRoomId }, - relations: { - chapter: { - module: { - course: true, - }, - }, - } - }); - const course = chatRoom.chapter.module.course; - const enrollment = await this.enrollmentService.findOne({ - user: { id: request.user.id }, - course: { id: course.id }, - }); - if (!enrollment) - throw new NotFoundException('You are not enrolled in this course'); - break; - case Role.TEACHER: - const teacher = await this.chatRoomService.findOne({ - where: { id: chatRoomId }, - relations: { - chapter: { - module: { - course: { - teacher: true, - }, - }, - }, - } - }); - if (teacher.chapter.module.course.teacher.id !== userId) - throw new NotFoundException('You are not the owner of this chat room'); - break; - default: - throw new ForbiddenException('You are not allowed to access this resource'); - } - return true; + async canActivate(context: ExecutionContext): Promise { + const request: AuthenticatedRequest = context.switchToHttp().getRequest(); + const userId = request.user.id; + const chatRoomId = request.params.id; + switch (request.user.role) { + case Role.STUDENT: + const chatRoom = await this.chatRoomService.findOne({ + where: { id: chatRoomId }, + relations: { + chapter: { + module: { + course: true, + }, + }, + }, + }); + const course = chatRoom.chapter.module.course; + const enrollment = await this.enrollmentService.findOne({ + user: { id: request.user.id }, + course: { id: course.id }, + }); + if (!enrollment) + throw new NotFoundException('You are not enrolled in this course'); + break; + case Role.TEACHER: + const teacher = await this.chatRoomService.findOne({ + where: { id: chatRoomId }, + relations: { + chapter: { + module: { + course: { + teacher: true, + }, + }, + }, + }, + }); + if (teacher.chapter.module.course.teacher.id !== userId) + throw new NotFoundException( + 'You are not the owner of this chat room', + ); + break; + default: + throw new ForbiddenException( + 'You are not allowed to access this resource', + ); } + return true; + } } diff --git a/src/course/course.service.ts b/src/course/course.service.ts index ba4d73c..d27d128 100644 --- a/src/course/course.service.ts +++ b/src/course/course.service.ts @@ -1,191 +1,191 @@ import { - BadRequestException, - Inject, - Injectable, - NotFoundException, + BadRequestException, + Inject, + Injectable, + NotFoundException, } from '@nestjs/common'; import { CourseStatus, Role } from 'src/shared/enums'; import { createPagination } from 'src/shared/pagination'; import { FindOneOptions, FindOptionsWhere, ILike, Repository } from 'typeorm'; import { Course } from './course.entity'; import { - CreateCourseDto, - PaginatedCourseResponeDto, - UpdateCourseDto, + CreateCourseDto, + PaginatedCourseResponeDto, + UpdateCourseDto, } from './dtos/index'; @Injectable() export class CourseService { - constructor( - @Inject('CourseRepository') - private readonly courseRepository: Repository, - ) { } - - async findAll({ - page = 1, - limit = 20, - search = '', - userId, - role, - }: { - page?: number; - limit?: number; - search?: string; - userId: string; - role: Role; - }): Promise { - const { find } = await createPagination(this.courseRepository, { - page, - limit, - }); - - const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; - const whereCondition = this.buildWhereCondition(userId, role, baseSearch); - - const courses = await find({ - where: whereCondition, - relations: { - teacher: true, - category: true, - }, - }).run(); - - return courses; + constructor( + @Inject('CourseRepository') + private readonly courseRepository: Repository, + ) {} + + async findAll({ + page = 1, + limit = 20, + search = '', + userId, + role, + }: { + page?: number; + limit?: number; + search?: string; + userId: string; + role: Role; + }): Promise { + const { find } = await createPagination(this.courseRepository, { + page, + limit, + }); + + const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; + const whereCondition = this.buildWhereCondition(userId, role, baseSearch); + + const courses = await find({ + where: whereCondition, + relations: { + teacher: true, + category: true, + }, + }).run(); + + return courses; + } + + async findOne( + userId: string, + role: Role, + options: FindOneOptions, + ): Promise { + const baseWhere = options.where as FindOptionsWhere; + const whereCondition = this.buildWhereCondition(userId, role, baseWhere); + + const course = await this.courseRepository.findOne({ + where: whereCondition, + relations: { + teacher: true, + category: true, + }, + }); + + if (!course) { + throw new NotFoundException('Course not found'); } - async findOne( - userId: string, - role: Role, - options: FindOneOptions, - ): Promise { - const baseWhere = options.where as FindOptionsWhere; - const whereCondition = this.buildWhereCondition(userId, role, baseWhere); - - const course = await this.courseRepository.findOne({ - where: whereCondition, - relations: { - teacher: true, - category: true, - }, - }); - - if (!course) { - throw new NotFoundException('Course not found'); - } - - return course; + return course; + } + + async create( + userId: string, + createCourseDto: CreateCourseDto, + ): Promise { + try { + const course = this.courseRepository.create({ + ...createCourseDto, + teacher: { id: userId }, + category: { id: createCourseDto.categoryId }, + }); + + return this.courseRepository.save(course); + } catch (error) { + if (error instanceof Error) { + throw new BadRequestException(error.message); + } } - - async create( - userId: string, - createCourseDto: CreateCourseDto, - ): Promise { - try { - const course = this.courseRepository.create({ - ...createCourseDto, - teacher: { id: userId }, - category: { id: createCourseDto.categoryId }, - }); - - return this.courseRepository.save(course); - } catch (error) { - if (error instanceof Error) { - throw new BadRequestException(error.message); - } - } - } - async update(id: string, updateCourseDto: UpdateCourseDto): Promise { - const existingCourse = await this.courseRepository.findOne({ - where: { id }, - relations: { - teacher: true, - category: true, - }, - }); - - if (!existingCourse) { - throw new NotFoundException('Course not found'); - } - - this.validateStatusTransition( - existingCourse.status, - updateCourseDto.status, - ); - - const updatedCourse = await this.courseRepository.save({ - ...existingCourse, - ...updateCourseDto, - category: { id: updateCourseDto.categoryId }, - }); - - return updatedCourse; + } + async update(id: string, updateCourseDto: UpdateCourseDto): Promise { + const existingCourse = await this.courseRepository.findOne({ + where: { id }, + relations: { + teacher: true, + category: true, + }, + }); + + if (!existingCourse) { + throw new NotFoundException('Course not found'); } - async delete(id: string): Promise { - try { - await this.courseRepository.delete(id); - } catch (error) { - throw new NotFoundException('Course not found'); - } + this.validateStatusTransition( + existingCourse.status, + updateCourseDto.status, + ); + + const updatedCourse = await this.courseRepository.save({ + ...existingCourse, + ...updateCourseDto, + category: { id: updateCourseDto.categoryId }, + }); + + return updatedCourse; + } + + async delete(id: string): Promise { + try { + await this.courseRepository.delete(id); + } catch (error) { + throw new NotFoundException('Course not found'); } - - private buildWhereCondition( - userId: string, - role: Role, - baseCondition: FindOptionsWhere = {}, - ): FindOptionsWhere | FindOptionsWhere[] { - const conditions: Record< - Role, - () => FindOptionsWhere | FindOptionsWhere[] - > = { - [Role.STUDENT]: () => ({ - ...baseCondition, - status: CourseStatus.PUBLISHED, - }), - [Role.TEACHER]: () => [ - { - ...baseCondition, - status: CourseStatus.PUBLISHED, - }, - { - ...baseCondition, - teacher: { id: userId }, - }, - ], - [Role.ADMIN]: () => baseCondition, - }; - - const buildCondition = conditions[role]; - - if (!buildCondition) { - throw new BadRequestException('Invalid role'); - } - - return buildCondition(); + } + + private buildWhereCondition( + userId: string, + role: Role, + baseCondition: FindOptionsWhere = {}, + ): FindOptionsWhere | FindOptionsWhere[] { + const conditions: Record< + Role, + () => FindOptionsWhere | FindOptionsWhere[] + > = { + [Role.STUDENT]: () => ({ + ...baseCondition, + status: CourseStatus.PUBLISHED, + }), + [Role.TEACHER]: () => [ + { + ...baseCondition, + status: CourseStatus.PUBLISHED, + }, + { + ...baseCondition, + teacher: { id: userId }, + }, + ], + [Role.ADMIN]: () => baseCondition, + }; + + const buildCondition = conditions[role]; + + if (!buildCondition) { + throw new BadRequestException('Invalid role'); } - private validateStatusTransition( - currentStatus: CourseStatus, - newStatus?: CourseStatus, - ): void { - if (!newStatus) return; - - if ( - currentStatus !== CourseStatus.DRAFT && - newStatus === CourseStatus.DRAFT - ) { - throw new BadRequestException( - `Cannot change status back to draft when current status is ${currentStatus}`, - ); - } - } - async validateOwnership(id: string, userId: string): Promise { - const course = await this.courseRepository.findOne({ - where: { id }, - relations: { teacher: true }, - }); - if (!course) throw new NotFoundException('Course not found'); - if (course.teacher.id !== userId) - throw new BadRequestException('You can only access your own courses'); + return buildCondition(); + } + + private validateStatusTransition( + currentStatus: CourseStatus, + newStatus?: CourseStatus, + ): void { + if (!newStatus) return; + + if ( + currentStatus !== CourseStatus.DRAFT && + newStatus === CourseStatus.DRAFT + ) { + throw new BadRequestException( + `Cannot change status back to draft when current status is ${currentStatus}`, + ); } + } + async validateOwnership(id: string, userId: string): Promise { + const course = await this.courseRepository.findOne({ + where: { id }, + relations: { teacher: true }, + }); + if (!course) throw new NotFoundException('Course not found'); + if (course.teacher.id !== userId) + throw new BadRequestException('You can only access your own courses'); + } } diff --git a/src/enrollment/enrollment.controller.ts b/src/enrollment/enrollment.controller.ts index 3ae3704..276d2ad 100644 --- a/src/enrollment/enrollment.controller.ts +++ b/src/enrollment/enrollment.controller.ts @@ -1,18 +1,18 @@ import { - BadRequestException, - Body, - Controller, - Delete, - Get, - HttpCode, - HttpStatus, - Injectable, - Param, - ParseUUIDPipe, - Patch, - Post, - Query, - Req, + BadRequestException, + Body, + Controller, + Delete, + Get, + HttpCode, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, } from '@nestjs/common'; import { ApiBearerAuth, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; @@ -21,8 +21,8 @@ import { Role } from 'src/shared/enums'; import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { CreateEnrollmentDto } from './dtos/create-enrollment.dto'; import { - EnrollmentResponseDto, - PaginatedEnrollmentResponseDto, + EnrollmentResponseDto, + PaginatedEnrollmentResponseDto, } from './dtos/enrollment-response.dto'; import { UpdateEnrollmentDto } from './dtos/update-enrollment.dto'; import { EnrollmentService } from './enrollment.service'; @@ -32,90 +32,90 @@ import { EnrollmentService } from './enrollment.service'; @ApiBearerAuth() @Injectable() export class EnrollmentController { - constructor(private readonly enrollmentService: EnrollmentService) { } + constructor(private readonly enrollmentService: EnrollmentService) {} - @Get() - @ApiResponse({ - status: HttpStatus.OK, - type: EnrollmentResponseDto, - description: 'Get all enrollments', - isArray: true, - }) - async findAll( - @Query() query: PaginateQueryDto, - ): Promise { - return this.enrollmentService.findAll(query); - } + @Get() + @ApiResponse({ + status: HttpStatus.OK, + type: EnrollmentResponseDto, + description: 'Get all enrollments', + isArray: true, + }) + async findAll( + @Query() query: PaginateQueryDto, + ): Promise { + return this.enrollmentService.findAll(query); + } - @Get(':id') - @ApiParam({ - name: 'id', - type: String, - description: 'Enrollment ID', - }) - @ApiResponse({ - status: HttpStatus.OK, - type: EnrollmentResponseDto, - description: 'Get enrollment by ID', - }) - async findOne( - @Param( - 'id', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - id: string, - ): Promise { - return await this.enrollmentService.findOne({ id }); - } + @Get(':id') + @ApiParam({ + name: 'id', + type: String, + description: 'Enrollment ID', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: EnrollmentResponseDto, + description: 'Get enrollment by ID', + }) + async findOne( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + return await this.enrollmentService.findOne({ id }); + } - @Post() - @Roles(Role.STUDENT) - @ApiResponse({ - status: HttpStatus.CREATED, - type: EnrollmentResponseDto, - description: 'Create enrollment', - }) - async create( - @Body() createEnrollmentDto: CreateEnrollmentDto, - ): Promise { - return await this.enrollmentService.create(createEnrollmentDto); - } + @Post() + @Roles(Role.STUDENT) + @ApiResponse({ + status: HttpStatus.CREATED, + type: EnrollmentResponseDto, + description: 'Create enrollment', + }) + async create( + @Body() createEnrollmentDto: CreateEnrollmentDto, + ): Promise { + return await this.enrollmentService.create(createEnrollmentDto); + } - @Patch(':id') - @Roles(Role.STUDENT) - @ApiParam({ - name: 'id', - type: String, - description: 'Enrollment ID', - }) - @ApiResponse({ - status: HttpStatus.OK, - type: EnrollmentResponseDto, - description: 'Update enrollment by ID', - }) - async update( - @Param('id', ParseUUIDPipe) id: string, - @Body() updateEnrollmentDto: UpdateEnrollmentDto, - ): Promise { - return await this.enrollmentService.update(id, updateEnrollmentDto); - } + @Patch(':id') + @Roles(Role.STUDENT) + @ApiParam({ + name: 'id', + type: String, + description: 'Enrollment ID', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: EnrollmentResponseDto, + description: 'Update enrollment by ID', + }) + async update( + @Param('id', ParseUUIDPipe) id: string, + @Body() updateEnrollmentDto: UpdateEnrollmentDto, + ): Promise { + return await this.enrollmentService.update(id, updateEnrollmentDto); + } - @Delete(':id') - @Roles(Role.STUDENT) - @ApiParam({ - name: 'id', - type: String, - description: 'Enrollment ID', - }) - @ApiResponse({ - status: HttpStatus.NO_CONTENT, - description: 'Delete enrollment by ID', - }) - @HttpCode(HttpStatus.NO_CONTENT) - async remove(@Param('id', ParseUUIDPipe) id: string): Promise { - return await this.enrollmentService.remove(id); - } + @Delete(':id') + @Roles(Role.STUDENT) + @ApiParam({ + name: 'id', + type: String, + description: 'Enrollment ID', + }) + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Delete enrollment by ID', + }) + @HttpCode(HttpStatus.NO_CONTENT) + async remove(@Param('id', ParseUUIDPipe) id: string): Promise { + return await this.enrollmentService.remove(id); + } } diff --git a/src/enrollment/enrollment.service.ts b/src/enrollment/enrollment.service.ts index 038c90a..fe26d13 100644 --- a/src/enrollment/enrollment.service.ts +++ b/src/enrollment/enrollment.service.ts @@ -1,8 +1,8 @@ import { - BadRequestException, - Injectable, - InternalServerErrorException, - NotFoundException, + BadRequestException, + Injectable, + InternalServerErrorException, + NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { createPagination } from 'src/shared/pagination'; @@ -15,84 +15,86 @@ import { EnrollmentStatus } from './enums/enrollment-status.enum'; @Injectable() export class EnrollmentService { - constructor( - @InjectRepository(Enrollment) - private readonly enrollmentRepository: Repository, - ) { } + constructor( + @InjectRepository(Enrollment) + private readonly enrollmentRepository: Repository, + ) {} - async findAll({ - page = 1, - limit = 20, - }: { - page?: number; - limit?: number; - }): Promise { - const { find } = await createPagination(this.enrollmentRepository, { - page, - limit, - }); + async findAll({ + page = 1, + limit = 20, + }: { + page?: number; + limit?: number; + }): Promise { + const { find } = await createPagination(this.enrollmentRepository, { + page, + limit, + }); - const enrollments = await find({ - relations: { - user: true, - course: true, - }, - }).run(); + const enrollments = await find({ + relations: { + user: true, + course: true, + }, + }).run(); - return enrollments; - } + return enrollments; + } - async findOne(where: FindOptionsWhere): Promise { - const options: FindOneOptions = { - where, - relations: { - user: true, - course: true, - }, - }; + async findOne(where: FindOptionsWhere): Promise { + const options: FindOneOptions = { + where, + relations: { + user: true, + course: true, + }, + }; - const enrollment = await this.enrollmentRepository.findOne(options); + const enrollment = await this.enrollmentRepository.findOne(options); - if (!enrollment) throw new NotFoundException('Enrollment not found'); + if (!enrollment) throw new NotFoundException('Enrollment not found'); - return enrollment; - } + return enrollment; + } - async create(createEnrollmentDto: CreateEnrollmentDto): Promise { - try { - const enrollment = await this.findOne({ - user: { id: createEnrollmentDto.userId }, - course: { id: createEnrollmentDto.courseId }, - }); - if (enrollment) throw new BadRequestException('Enrollment already exists'); - const createdEnrollment = this.enrollmentRepository.create(createEnrollmentDto); - return await this.enrollmentRepository.save({ - ...createdEnrollment, - user: { id: createEnrollmentDto.userId }, - course: { id: createEnrollmentDto.courseId }, - }); - } catch (error) { - throw new InternalServerErrorException(error.message); - } + async create(createEnrollmentDto: CreateEnrollmentDto): Promise { + try { + const enrollment = await this.findOne({ + user: { id: createEnrollmentDto.userId }, + course: { id: createEnrollmentDto.courseId }, + }); + if (enrollment) + throw new BadRequestException('Enrollment already exists'); + const createdEnrollment = + this.enrollmentRepository.create(createEnrollmentDto); + return await this.enrollmentRepository.save({ + ...createdEnrollment, + user: { id: createEnrollmentDto.userId }, + course: { id: createEnrollmentDto.courseId }, + }); + } catch (error) { + throw new InternalServerErrorException(error.message); } + } - async update( - id: string, - updateEnrollmentDto: UpdateEnrollmentDto, - ): Promise { - const enrollment = await this.findOne({ - status: EnrollmentStatus.ACTIVE, - id, - }); - this.enrollmentRepository.merge(enrollment, updateEnrollmentDto); - await this.enrollmentRepository.save(enrollment); - return enrollment; - } + async update( + id: string, + updateEnrollmentDto: UpdateEnrollmentDto, + ): Promise { + const enrollment = await this.findOne({ + status: EnrollmentStatus.ACTIVE, + id, + }); + this.enrollmentRepository.merge(enrollment, updateEnrollmentDto); + await this.enrollmentRepository.save(enrollment); + return enrollment; + } - async remove(id: string): Promise { - const enrollment = await this.findOne({ - id, - }); - await this.enrollmentRepository.remove(enrollment); - } + async remove(id: string): Promise { + const enrollment = await this.findOne({ + id, + }); + await this.enrollmentRepository.remove(enrollment); + } } diff --git a/src/shared/configs/dotenv.config.ts b/src/shared/configs/dotenv.config.ts index 4dd0873..283fa28 100644 --- a/src/shared/configs/dotenv.config.ts +++ b/src/shared/configs/dotenv.config.ts @@ -2,30 +2,30 @@ import * as Joi from 'joi'; import { Environment } from '../enums/environment.enum'; export const dotenvConfig = Joi.object({ - PORT: Joi.number().required(), - NODE_ENV: Joi.string() - .valid(...Object.values(Environment)) - .default(Environment.DEVELOPMENT), - IS_DEVELOPMENT: Joi.boolean().when('NODE_ENV', { - is: Joi.equal(Environment.DEVELOPMENT), - then: Joi.boolean().default(true), - otherwise: Joi.boolean().default(false), - }), - DB_HOST: Joi.string().required(), - DB_PORT: Joi.number().required(), - DB_USERNAME: Joi.string().required(), - DB_PASSWORD: Joi.string().required(), - DB_DATABASE: Joi.string().required(), - CORS_ALLOW_ORIGIN: Joi.string().required(), - JWT_ACCESS_SECRET: Joi.string().required(), - JWT_REFRESH_SECRET: Joi.string().required(), - JWT_ACCESS_EXPIRATION: Joi.string().required(), - JWT_REFRESH_EXPIRATION: Joi.string().required(), - AWS_ACCESS_KEY_ID: Joi.string().required(), - AWS_SECRET_ACCESS_KEY: Joi.string().required(), - AWS_REGION: Joi.string().required(), - AWS_BUCKET_NAME: Joi.string().required(), + PORT: Joi.number().required(), + NODE_ENV: Joi.string() + .valid(...Object.values(Environment)) + .default(Environment.DEVELOPMENT), + IS_DEVELOPMENT: Joi.boolean().when('NODE_ENV', { + is: Joi.equal(Environment.DEVELOPMENT), + then: Joi.boolean().default(true), + otherwise: Joi.boolean().default(false), + }), + DB_HOST: Joi.string().required(), + DB_PORT: Joi.number().required(), + DB_USERNAME: Joi.string().required(), + DB_PASSWORD: Joi.string().required(), + DB_DATABASE: Joi.string().required(), + CORS_ALLOW_ORIGIN: Joi.string().required(), + JWT_ACCESS_SECRET: Joi.string().required(), + JWT_REFRESH_SECRET: Joi.string().required(), + JWT_ACCESS_EXPIRATION: Joi.string().required(), + JWT_REFRESH_EXPIRATION: Joi.string().required(), + AWS_ACCESS_KEY_ID: Joi.string().required(), + AWS_SECRET_ACCESS_KEY: Joi.string().required(), + AWS_REGION: Joi.string().required(), + AWS_BUCKET_NAME: Joi.string().required(), ADMIN_EMAIL: Joi.string().required(), ADMIN_PASSWORD: Joi.string().required(), - JWT_AI_ACCESS_SECRET: Joi.string().required(), + JWT_AI_ACCESS_SECRET: Joi.string().required(), }); diff --git a/src/shared/constants/global-config.constant.ts b/src/shared/constants/global-config.constant.ts index 6cafd15..96ecf59 100644 --- a/src/shared/constants/global-config.constant.ts +++ b/src/shared/constants/global-config.constant.ts @@ -1,22 +1,22 @@ export const GLOBAL_CONFIG = { - PORT: 'PORT', - NODE_ENV: 'NODE_ENV', - IS_DEVELOPMENT: 'IS_DEVELOPMENT', - DB_HOST: 'DB_HOST', - DB_PORT: 'DB_PORT', - DB_USERNAME: 'DB_USERNAME', - DB_PASSWORD: 'DB_PASSWORD', - DB_DATABASE: 'DB_DATABASE', - CORS_ALLOW_ORIGIN: 'CORS_ALLOW_ORIGIN', - JWT_ACCESS_SECRET: 'JWT_ACCESS_SECRET', - JWT_REFRESH_SECRET: 'JWT_REFRESH_SECRET', - JWT_ACCESS_EXPIRATION: 'JWT_ACCESS_EXPIRATION', - JWT_REFRESH_EXPIRATION: 'JWT_REFRESH_EXPIRATION', - AWS_ACCESS_KEY_ID: 'AWS_ACCESS_KEY_ID', - AWS_SECRET_ACCESS_KEY: 'AWS_SECRET_ACCESS_KEY', - AWS_REGION: 'AWS_REGION', - AWS_BUCKET_NAME: 'AWS_BUCKET_NAME', + PORT: 'PORT', + NODE_ENV: 'NODE_ENV', + IS_DEVELOPMENT: 'IS_DEVELOPMENT', + DB_HOST: 'DB_HOST', + DB_PORT: 'DB_PORT', + DB_USERNAME: 'DB_USERNAME', + DB_PASSWORD: 'DB_PASSWORD', + DB_DATABASE: 'DB_DATABASE', + CORS_ALLOW_ORIGIN: 'CORS_ALLOW_ORIGIN', + JWT_ACCESS_SECRET: 'JWT_ACCESS_SECRET', + JWT_REFRESH_SECRET: 'JWT_REFRESH_SECRET', + JWT_ACCESS_EXPIRATION: 'JWT_ACCESS_EXPIRATION', + JWT_REFRESH_EXPIRATION: 'JWT_REFRESH_EXPIRATION', + AWS_ACCESS_KEY_ID: 'AWS_ACCESS_KEY_ID', + AWS_SECRET_ACCESS_KEY: 'AWS_SECRET_ACCESS_KEY', + AWS_REGION: 'AWS_REGION', + AWS_BUCKET_NAME: 'AWS_BUCKET_NAME', ADMIN_EMAIL: 'ADMIN_EMAIL', ADMIN_PASSWORD: 'ADMIN_PASSWORD', - JWT_AI_ACCESS_SECRET: 'JWT_AI_ACCESS_SECRET', + JWT_AI_ACCESS_SECRET: 'JWT_AI_ACCESS_SECRET', }; diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 82752a9..ea34624 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,9 +1,9 @@ import { - BadRequestException, - Inject, - Injectable, - NotFoundException, - OnModuleInit, + BadRequestException, + Inject, + Injectable, + NotFoundException, + OnModuleInit, } from '@nestjs/common'; import { createPagination } from 'src/shared/pagination'; import { FindOneOptions, ILike, Repository, FindOptionsWhere } from 'typeorm'; @@ -17,79 +17,79 @@ import { hash } from 'argon2'; @Injectable() export class UserService implements OnModuleInit { - constructor( - @Inject('UserRepository') - private readonly userRepository: Repository, - private readonly configService: ConfigService, - ) { } + constructor( + @Inject('UserRepository') + private readonly userRepository: Repository, + private readonly configService: ConfigService, + ) {} - async onModuleInit() { - const adminEmail = this.configService.get('ADMIN_EMAIL'); - const adminPassword = this.configService.get('ADMIN_PASSWORD'); - const admin = await this.userRepository.findOne({ - where: { email: adminEmail }, - }); - if (!admin) { - await this.userRepository.save({ - email: adminEmail, - password: await hash(adminPassword), - fullname: 'Admin', - role: Role.ADMIN, - username: 'admin', - }); - } + async onModuleInit() { + const adminEmail = this.configService.get('ADMIN_EMAIL'); + const adminPassword = this.configService.get('ADMIN_PASSWORD'); + const admin = await this.userRepository.findOne({ + where: { email: adminEmail }, + }); + if (!admin) { + await this.userRepository.save({ + email: adminEmail, + password: await hash(adminPassword), + fullname: 'Admin', + role: Role.ADMIN, + username: 'admin', + }); } + } - async findAll({ - page = 1, - limit = 20, - search = '', - }: { - page?: number; - limit?: number; - search?: string; - }): Promise { - const { find } = await createPagination(this.userRepository, { - page, - limit, - }); + async findAll({ + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }): Promise { + const { find } = await createPagination(this.userRepository, { + page, + limit, + }); - const users = await find({ - where: { email: ILike(`%${search}%`) }, - }).run(); + const users = await find({ + where: { email: ILike(`%${search}%`) }, + }).run(); - return users; - } + return users; + } - async findOne(options: FindOneOptions): Promise { - return await this.userRepository.findOne(options); - } + async findOne(options: FindOneOptions): Promise { + return await this.userRepository.findOne(options); + } - async create(createUserDto: CreateUserDto): Promise { - try { - return await this.userRepository.save(createUserDto); - } catch (error) { - if (error instanceof Error) throw new BadRequestException(error.message); - } + async create(createUserDto: CreateUserDto): Promise { + try { + return await this.userRepository.save(createUserDto); + } catch (error) { + if (error instanceof Error) throw new BadRequestException(error.message); } + } - async update( - id: string, - partialEntity: QueryDeepPartialEntity, - ): Promise { - try { - await this.userRepository.update(id, partialEntity); - return await this.findOne({ where: { id } }); - } catch (error) { - if (error instanceof Error) throw new NotFoundException('User not found'); - } + async update( + id: string, + partialEntity: QueryDeepPartialEntity, + ): Promise { + try { + await this.userRepository.update(id, partialEntity); + return await this.findOne({ where: { id } }); + } catch (error) { + if (error instanceof Error) throw new NotFoundException('User not found'); } + } - async delete(criteria: FindOptionsWhere): Promise { - try { - await this.userRepository.delete(criteria); - } catch (error) { - if (error instanceof Error) throw new NotFoundException('User not found'); - } + async delete(criteria: FindOptionsWhere): Promise { + try { + await this.userRepository.delete(criteria); + } catch (error) { + if (error instanceof Error) throw new NotFoundException('User not found'); } + } } From d223fa6a2c91f880015c17e9c96cfb43215148a2 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Tue, 19 Nov 2024 16:08:21 +0700 Subject: [PATCH 089/155] refactor: remove JWT_AI_ACCESS_SECRET from environment configuration and related files --- .env.example | 1 - src/shared/configs/dotenv.config.ts | 1 - src/shared/constants/global-config.constant.ts | 1 - 3 files changed, 3 deletions(-) diff --git a/.env.example b/.env.example index a005e10..a9bf602 100644 --- a/.env.example +++ b/.env.example @@ -10,7 +10,6 @@ JWT_ACCESS_SECRET=+W5LRxRFUk8MKSHMeRYevRg JWT_REFRESH_SECRET=2Z7bB8GizN15kaHzB+H8Tg JWT_ACCESS_EXPIRATION=1d JWT_REFRESH_EXPIRATION=7d -JWT_AI_ACCESS_SECRET=2Z7bB8GizN15kaHzB+H8Tg AWS_ACCESS_KEY_ID=youraccesskey AWS_SECRET_ACCESS_KEY=yoursecretkey AWS_REGION=yourregion diff --git a/src/shared/configs/dotenv.config.ts b/src/shared/configs/dotenv.config.ts index 283fa28..7c8e00c 100644 --- a/src/shared/configs/dotenv.config.ts +++ b/src/shared/configs/dotenv.config.ts @@ -27,5 +27,4 @@ export const dotenvConfig = Joi.object({ AWS_BUCKET_NAME: Joi.string().required(), ADMIN_EMAIL: Joi.string().required(), ADMIN_PASSWORD: Joi.string().required(), - JWT_AI_ACCESS_SECRET: Joi.string().required(), }); diff --git a/src/shared/constants/global-config.constant.ts b/src/shared/constants/global-config.constant.ts index 96ecf59..74dae04 100644 --- a/src/shared/constants/global-config.constant.ts +++ b/src/shared/constants/global-config.constant.ts @@ -18,5 +18,4 @@ export const GLOBAL_CONFIG = { AWS_BUCKET_NAME: 'AWS_BUCKET_NAME', ADMIN_EMAIL: 'ADMIN_EMAIL', ADMIN_PASSWORD: 'ADMIN_PASSWORD', - JWT_AI_ACCESS_SECRET: 'JWT_AI_ACCESS_SECRET', }; From 3139e3be8e54576c3edf5a9e9e2ac7d6ed3c4fc3 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Tue, 19 Nov 2024 16:36:33 +0700 Subject: [PATCH 090/155] feat: change input in update dto --- .../dtos/update-exam-answer.dto.ts | 6 +- src/exam-answer/exam-answer.service.ts | 22 ------ .../dtos/update-exam-attempt.dto.ts | 29 ++------ src/exam/dtos/exam-response.dto.ts | 12 +++- src/exam/dtos/update-exam.dto.ts | 6 +- src/exam/exam.service.ts | 47 ++++++++----- .../dtos/question-option-response.dto.ts | 17 +++-- .../dtos/update-question-option.dto.ts | 4 +- .../question-option.service.ts | 65 +++++++++++++----- src/question/dtos/question-response.dto.ts | 16 +++-- src/question/dtos/update-question.dto.ts | 6 +- src/question/question.service.ts | 67 +++++++++++++------ 12 files changed, 176 insertions(+), 121 deletions(-) diff --git a/src/exam-answer/dtos/update-exam-answer.dto.ts b/src/exam-answer/dtos/update-exam-answer.dto.ts index 7ae39b6..c4d4b52 100644 --- a/src/exam-answer/dtos/update-exam-answer.dto.ts +++ b/src/exam-answer/dtos/update-exam-answer.dto.ts @@ -1,4 +1,6 @@ -import { PartialType } from '@nestjs/swagger'; +import { OmitType, PartialType } from '@nestjs/swagger'; import { CreateExamAnswerDto } from './create-exam-answer.dto'; -export class UpdateExamAnswerDto extends PartialType(CreateExamAnswerDto) {} +export class UpdateExamAnswerDto extends PartialType( + OmitType(CreateExamAnswerDto, ['selectedOptionId'] as const), +) {} diff --git a/src/exam-answer/exam-answer.service.ts b/src/exam-answer/exam-answer.service.ts index 7386274..b115f61 100644 --- a/src/exam-answer/exam-answer.service.ts +++ b/src/exam-answer/exam-answer.service.ts @@ -301,8 +301,6 @@ export class ExamAnswerService { if (!examAnswerInData) throw new NotFoundException('Exam answer not found'); let examAttempt = null; - let selectedOption = null; - let question = null; if (updateExamAnswerDto.examAttemptId) { examAttempt = await this.examAttemptRepository.findOne({ where: { id: updateExamAnswerDto.examAttemptId }, @@ -312,30 +310,10 @@ export class ExamAnswerService { throw new NotFoundException('Exam attempt not found'); } } - if (updateExamAnswerDto.selectedOptionId) { - selectedOption = await this.questionOptionRepository.findOne({ - where: { id: updateExamAnswerDto.selectedOptionId }, - select: this.selectPopulateSelectedOption(), - }); - if (!selectedOption) { - throw new NotFoundException('SelectedOption not found'); - } - - question = await this.questionRepository.findOne({ - where: { id: selectedOption.questionId }, - select: this.selectPopulateQuestion(), - }); - - if (!question) { - throw new NotFoundException('Question not found'); - } - } const updateExamAnswer = { ...updateExamAnswerDto, ...(examAttempt ? { examAttemptId: examAttempt.id } : {}), - ...(selectedOption ? { selectedOptionId: selectedOption.id } : {}), - ...(question ? { questionId: selectedOption.questionId } : {}), }; const examAnswer = await this.examAnswerRepository.update( diff --git a/src/exam-attempt/dtos/update-exam-attempt.dto.ts b/src/exam-attempt/dtos/update-exam-attempt.dto.ts index 52f1b02..c6a0cf3 100644 --- a/src/exam-attempt/dtos/update-exam-attempt.dto.ts +++ b/src/exam-attempt/dtos/update-exam-attempt.dto.ts @@ -1,25 +1,6 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsEnum, IsInt, IsNotEmpty, Min } from 'class-validator'; -import { ExamAttemptStatus } from 'src/shared/enums'; +import { OmitType, PartialType } from '@nestjs/swagger'; +import { CreateExamAttemptDto } from './create-exam-attempt.dto'; -export class UpdateExamAttemptDto { - @IsNotEmpty() - @Min(0) - @IsInt() - @ApiProperty({ - description: 'Score', - type: Number, - example: 0, - }) - score: number = 0; - - @IsNotEmpty() - @IsEnum(ExamAttemptStatus) - @ApiProperty({ - description: 'Exam attempt status', - type: String, - example: ExamAttemptStatus.IN_PROGRESS, - enum: ExamAttemptStatus, - }) - status: ExamAttemptStatus; -} +export class UpdateExamAttemptDto extends PartialType( + OmitType(CreateExamAttemptDto, ['examId'] as const), +) {} diff --git a/src/exam/dtos/exam-response.dto.ts b/src/exam/dtos/exam-response.dto.ts index 7500666..12cadc2 100644 --- a/src/exam/dtos/exam-response.dto.ts +++ b/src/exam/dtos/exam-response.dto.ts @@ -16,10 +16,16 @@ export class ExamResponseDto { description: 'Course Module Data', type: String, example: { - id: 'b88d68fc-7437-4812-b4f6-e08f18bc09d1', - title: 'Thai', - description: 'This module is an introduction to programming', + id: '7093a5ae-cc1d-4017-8445-cba7ea978b22', + title: 'Introduction to Biology', + description: 'This module is an introduction to biology', orderIndex: 1, + course: { + id: 'b7634715-9536-46be-ae06-650dc0d719fb', + teacher: { + id: '75af7b82-d765-40a3-82aa-bc4f572c492c', + }, + }, }, }) courseModule: CourseModuleResponseDto; diff --git a/src/exam/dtos/update-exam.dto.ts b/src/exam/dtos/update-exam.dto.ts index 3199396..cd18912 100644 --- a/src/exam/dtos/update-exam.dto.ts +++ b/src/exam/dtos/update-exam.dto.ts @@ -1,4 +1,6 @@ -import { PartialType } from '@nestjs/swagger'; +import { OmitType, PartialType } from '@nestjs/swagger'; import { CreateExamDto } from './create-exam.dto'; -export class UpdateExamDto extends PartialType(CreateExamDto) {} +export class UpdateExamDto extends PartialType( + OmitType(CreateExamDto, ['courseModuleId'] as const), +) {} diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts index 38f252b..55e8b79 100644 --- a/src/exam/exam.service.ts +++ b/src/exam/exam.service.ts @@ -55,7 +55,11 @@ export class ExamService { ); const exam = await find({ where: whereCondition, - relations: ['courseModule'], + relations: [ + 'courseModule', + 'courseModule.course', + 'courseModule.course.teacher', + ], select: { courseModule: this.selectPopulateCourseModule(), }, @@ -123,7 +127,11 @@ export class ExamService { const exam = await this.examRepository.findOne({ ...options, where, - relations: ['courseModule'], + relations: [ + 'courseModule', + 'courseModule.course', + 'courseModule.course.teacher', + ], select: { courseModule: this.selectPopulateCourseModule(), }, @@ -140,6 +148,7 @@ export class ExamService { const courseModule = await this.courseModuleRepository.findOne({ where: { id: createExamDto.courseModuleId }, select: this.selectPopulateCourseModule(), + relations: ['course', 'course.teacher'], }); if (!courseModule) throw new NotFoundException('Course Module not found'); @@ -169,25 +178,16 @@ export class ExamService { ) { throw new ForbiddenException("Can't change status to draft"); } - let courseModule = null; - if (updateExamDto.courseModuleId) { - courseModule = await this.courseModuleRepository.findOne({ - where: { id: updateExamDto.courseModuleId }, - select: this.selectPopulateCourseModule(), - }); - - if (!courseModule) throw new NotFoundException('CourseModule Not Found'); - } - const updateExam = { - ...updateExamDto, - ...(courseModule ? { examAttemptId: courseModule.id } : {}), - }; - const exam = await this.examRepository.update(id, updateExam); + const exam = await this.examRepository.update(id, updateExamDto); if (!exam) throw new BadRequestException("Can't update exam"); return await this.examRepository.findOne({ where: { id }, - relations: ['courseModule'], + relations: [ + 'courseModule', + 'courseModule.course', + 'courseModule.course.teacher', + ], select: { courseModule: this.selectPopulateCourseModule(), }, @@ -206,7 +206,18 @@ export class ExamService { } private selectPopulateCourseModule(): FindOptionsSelect { - return { id: true, title: true, description: true, orderIndex: true }; + return { + id: true, + title: true, + description: true, + orderIndex: true, + course: { + id: true, + teacher: { + id: true, + }, + }, + }; } private checkPermission(userId: string, role: Role, exam: Exam): boolean { diff --git a/src/question-option/dtos/question-option-response.dto.ts b/src/question-option/dtos/question-option-response.dto.ts index 431fe40..7b2df6c 100644 --- a/src/question-option/dtos/question-option-response.dto.ts +++ b/src/question-option/dtos/question-option-response.dto.ts @@ -15,11 +15,18 @@ export class QuestionOptionResponseDto { description: 'Question Data', type: Question, example: { - id: '3a3013bb-b13c-40f0-be93-1ff7ad3e36f0', - question: 'What the fick?', - type: 'Open question', - points: 0, - orderIndex: 1, + id: 'e20ffe51-2514-4f14-9bea-4bb28bb97fdd', + courseModuleId: '7093a5ae-cc1d-4017-8445-cba7ea978b22', + courseModule: { + id: '7093a5ae-cc1d-4017-8445-cba7ea978b22', + courseId: 'b7634715-9536-46be-ae06-650dc0d719fb', + course: { + id: 'b7634715-9536-46be-ae06-650dc0d719fb', + teacher: { + id: '75af7b82-d765-40a3-82aa-bc4f572c492c', + }, + }, + }, }, }) question: Question; diff --git a/src/question-option/dtos/update-question-option.dto.ts b/src/question-option/dtos/update-question-option.dto.ts index f349488..97d64a5 100644 --- a/src/question-option/dtos/update-question-option.dto.ts +++ b/src/question-option/dtos/update-question-option.dto.ts @@ -1,6 +1,6 @@ -import { PartialType } from '@nestjs/swagger'; +import { OmitType, PartialType } from '@nestjs/swagger'; import { CreateQuestionOptionDto } from './create-question-option.dto'; export class UpdateQuestionOptionDto extends PartialType( - CreateQuestionOptionDto, + OmitType(CreateQuestionOptionDto, ['questionId'] as const), ) {} diff --git a/src/question-option/question-option.service.ts b/src/question-option/question-option.service.ts index 88a30d3..4f038c9 100644 --- a/src/question-option/question-option.service.ts +++ b/src/question-option/question-option.service.ts @@ -54,7 +54,13 @@ export class QuestionOptionService { ); const question = await find({ where: whereCondition, - relations: ['question'], + relations: [ + 'question', + 'question.exam', + 'question.exam.courseModule', + 'question.exam.courseModule.course', + 'question.exam.courseModule.course.teacher', + ], select: { question: this.selectPopulateQuestion(), }, @@ -80,7 +86,13 @@ export class QuestionOptionService { const question = await this.questionOptionRepository.findOne({ ...options, where, - relations: ['question'], + relations: [ + 'question', + 'question.exam', + 'question.exam.courseModule', + 'question.exam.courseModule.course', + 'question.exam.courseModule.course.teacher', + ], select: { question: this.selectPopulateQuestion(), }, @@ -129,7 +141,13 @@ export class QuestionOptionService { const questionOption = await find({ where: whereCondition, - relations: ['question'], + relations: [ + 'question', + 'question.exam', + 'question.exam.courseModule', + 'question.exam.courseModule.course', + 'question.exam.courseModule.course.teacher', + ], select: { question: this.selectPopulateQuestion(), }, @@ -195,6 +213,12 @@ export class QuestionOptionService { const question = await this.questionRepository.findOne({ where: { id: createQuestionOptionDto.questionId }, select: this.selectPopulateQuestion(), + relations: [ + 'exam', + 'exam.courseModule', + 'exam.courseModule.course', + 'exam.courseModule.course.teacher', + ], }); if (!question) { throw new NotFoundException('Question option not found.'); @@ -221,28 +245,21 @@ export class QuestionOptionService { }); if (this.checkPermission(userId, role, questionOptionInData) === false) throw new ForbiddenException('Can not change this question option'); - let question = null; - if (updateQuestionOptionDto.questionId) { - question = await this.questionRepository.findOne({ - where: { id: updateQuestionOptionDto.questionId }, - select: this.selectPopulateQuestion(), - }); - - if (!question) throw new NotFoundException('Question Not Found'); - } - const updateQuestionOption = { - ...updateQuestionOptionDto, - ...(question ? { questionId: question.id } : {}), - }; const questionOption = await this.questionOptionRepository.update( id, - updateQuestionOption, + updateQuestionOptionDto, ); if (!questionOption) throw new BadRequestException("Can't update question option"); return await this.questionOptionRepository.findOne({ where: { id }, - relations: ['question'], + relations: [ + 'question', + 'question.exam', + 'question.exam.courseModule', + 'question.exam.courseModule.course', + 'question.exam.courseModule.course.teacher', + ], select: { question: this.selectPopulateQuestion(), }, @@ -274,6 +291,18 @@ export class QuestionOptionService { type: true, points: true, orderIndex: true, + exam: { + id: true, + courseModule: { + id: true, + course: { + id: true, + teacher: { + id: true, + }, + }, + }, + }, }; } diff --git a/src/question/dtos/question-response.dto.ts b/src/question/dtos/question-response.dto.ts index 8105f41..407ae81 100644 --- a/src/question/dtos/question-response.dto.ts +++ b/src/question/dtos/question-response.dto.ts @@ -17,15 +17,23 @@ export class QuestionResponseDto { description: 'Exam Data', type: ExamResponseDto, example: { - id: 'ce2fd59a-28ea-4192-bfc6-c2347450ab7e', - courseModuleId: 'b88d68fc-7437-4812-b4f6-e08f18bc09d1', + id: 'e20ffe51-2514-4f14-9bea-4bb28bb97fdd', title: 'Biology', description: 'This course is an introduction to biology', timeLimit: 20, passingScore: 50, maxAttempts: 1, - shuffleQuestions: true, - status: 'draft', + shuffleQuestions: false, + status: 'published', + courseModule: { + id: '7093a5ae-cc1d-4017-8445-cba7ea978b22', + course: { + id: 'b7634715-9536-46be-ae06-650dc0d719fb', + teacher: { + id: '75af7b82-d765-40a3-82aa-bc4f572c492c', + }, + }, + }, }, }) exam: Exam; diff --git a/src/question/dtos/update-question.dto.ts b/src/question/dtos/update-question.dto.ts index 97fa0b0..94c4a43 100644 --- a/src/question/dtos/update-question.dto.ts +++ b/src/question/dtos/update-question.dto.ts @@ -1,4 +1,6 @@ -import { PartialType } from '@nestjs/swagger'; +import { OmitType, PartialType } from '@nestjs/swagger'; import { CreateQuestionDto } from './create-question.dto'; -export class UpdateQuestionDto extends PartialType(CreateQuestionDto) {} +export class UpdateQuestionDto extends PartialType( + OmitType(CreateQuestionDto, ['examId'] as const), +) {} diff --git a/src/question/question.service.ts b/src/question/question.service.ts index 1f57b48..00a8424 100644 --- a/src/question/question.service.ts +++ b/src/question/question.service.ts @@ -57,7 +57,12 @@ export class QuestionService { orderIndex: 'ASC', }, where: whereCondition, - relations: ['exam'], + relations: [ + 'exam', + 'exam.courseModule', + 'exam.courseModule.course', + 'exam.courseModule.course.teacher', + ], select: { exam: this.selectPopulateExam(), }, @@ -83,7 +88,12 @@ export class QuestionService { const question = await this.questionRepository.findOne({ ...options, where, - relations: ['exam'], + relations: [ + 'exam', + 'exam.courseModule', + 'exam.courseModule.course', + 'exam.courseModule.course.teacher', + ], select: { exam: this.selectPopulateExam(), }, @@ -136,7 +146,12 @@ export class QuestionService { orderIndex: 'ASC', }, where: whereCondition, - relations: ['exam'], + relations: [ + 'exam', + 'exam.courseModule', + 'exam.courseModule.course', + 'exam.courseModule.course.teacher', + ], select: { exam: this.selectPopulateExam(), }, @@ -154,7 +169,12 @@ export class QuestionService { ...{ __queryBuilder: baseQuery, }, - relations: ['exam'], + relations: [ + 'exam', + 'exam.courseModule', + 'exam.courseModule.course', + 'exam.courseModule.course.teacher', + ], select: { exam: this.selectPopulateExam(), }, @@ -240,6 +260,11 @@ export class QuestionService { const exam = await this.examRepository.findOne({ where: { id: createQuestionDto.examId }, select: this.selectPopulateExam(), + relations: [ + 'courseModule', + 'courseModule.course', + 'courseModule.course.teacher', + ], }); if (!exam) { throw new NotFoundException('Exam not found.'); @@ -273,25 +298,20 @@ export class QuestionService { const question = await this.findOne(userId, role, { where: { id } }); if (this.checkPermission(userId, role, question) === false) throw new BadRequestException('Can not change this question'); - let exam = null; - if (updateQuestionDto.examId) { - exam = await this.examRepository.findOne({ - where: { id: updateQuestionDto.examId }, - select: this.selectPopulateExam(), - }); - - if (!exam) throw new NotFoundException('Exam Not Found'); - } - const updateQuestion = { - ...updateQuestionDto, - ...(exam ? { examId: exam.id } : {}), - }; try { - const question = await this.questionRepository.update(id, updateQuestion); + const question = await this.questionRepository.update( + id, + updateQuestionDto, + ); if (!question) throw new BadRequestException("Can't update question"); return await this.questionRepository.findOne({ where: { id }, - relations: ['exam'], + relations: [ + 'exam', + 'exam.courseModule', + 'exam.courseModule.course', + 'exam.courseModule.course.teacher', + ], select: { exam: this.selectPopulateExam(), }, @@ -331,6 +351,15 @@ export class QuestionService { maxAttempts: true, shuffleQuestions: true, status: true, + courseModule: { + id: true, + course: { + id: true, + teacher: { + id: true, + }, + }, + }, }; } From e3e5b33f9d6aa6bf1b3a591f458cc1dcb8c02ba5 Mon Sep 17 00:00:00 2001 From: Potsawee Date: Wed, 20 Nov 2024 14:49:16 +0700 Subject: [PATCH 091/155] add pagination and upload thumbnail --- src/app.module.ts | 2 +- src/reward/dtos/reward-response.dto.ts | 16 +- src/reward/dtos/update-reward.dto.ts | 4 +- src/reward/reward.controllers.ts | 153 +++++++++++++++--- src/reward/reward.entity.ts | 2 +- src/reward/reward.module.ts | 5 +- src/reward/reward.service.ts | 42 ++++- src/shared/configs/database.config.ts | 2 +- .../dtos/update-status-user-reward.dto.ts | 0 .../dtos/update-user-reward.dto.ts | 0 .../dtos/user-reward-response.dto.ts | 0 .../enums/user-reward-status.enum.ts | 0 .../user-reward.controllers.ts | 4 +- .../user-reward.entity.ts | 0 .../user-reward.module.ts | 0 .../user-reward.providers.ts | 0 .../user-reward.service.ts | 0 src/user/user.entity.ts | 2 +- 18 files changed, 199 insertions(+), 33 deletions(-) rename src/{userReward => user-reward}/dtos/update-status-user-reward.dto.ts (100%) rename src/{userReward => user-reward}/dtos/update-user-reward.dto.ts (100%) rename src/{userReward => user-reward}/dtos/user-reward-response.dto.ts (100%) rename src/{userReward => user-reward}/enums/user-reward-status.enum.ts (100%) rename src/{userReward => user-reward}/user-reward.controllers.ts (97%) rename src/{userReward => user-reward}/user-reward.entity.ts (100%) rename src/{userReward => user-reward}/user-reward.module.ts (100%) rename src/{userReward => user-reward}/user-reward.providers.ts (100%) rename src/{userReward => user-reward}/user-reward.service.ts (100%) diff --git a/src/app.module.ts b/src/app.module.ts index cb6456d..33255b5 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -33,7 +33,7 @@ import { UserBackgroundModule } from './user-background/user-background.module'; import { UserOccupationModule } from './user-occupation/user-occupation.module'; import { UserStreakModule } from './user-streak/user-streak.module'; import { UserModule } from './user/user.module'; -import { UserRewardModule } from './userReward/user-reward.module'; +import { UserRewardModule } from './user-reward/user-reward.module'; @Module({ imports: [ diff --git a/src/reward/dtos/reward-response.dto.ts b/src/reward/dtos/reward-response.dto.ts index 482b3f7..7c17bf3 100644 --- a/src/reward/dtos/reward-response.dto.ts +++ b/src/reward/dtos/reward-response.dto.ts @@ -2,6 +2,7 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { Status } from '../enums/status.enum'; import { Type } from '../enums/type.enum'; import { Reward } from '../reward.entity'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; export class RewardResponseDto { @ApiProperty({ @@ -21,7 +22,7 @@ export class RewardResponseDto { @ApiPropertyOptional({ description: 'description of reward (optional)', type: String, - example: 'get 15% of in next corse', + example: 'get 15% of in next course', }) description?: string; @@ -89,3 +90,16 @@ export class RewardResponseDto { this.updatedAt = reward.updatedAt; } } +export class PaginatedRewardResponseDto extends PaginatedResponse( + RewardResponseDto, +) { + constructor( + rewards: Reward[], + total: number, + pageSize: number, + currentPage: number, + ) { + const rewardDtos = rewards.map((reward) => new RewardResponseDto(reward)); + super(rewardDtos, total, pageSize, currentPage); + } +} diff --git a/src/reward/dtos/update-reward.dto.ts b/src/reward/dtos/update-reward.dto.ts index e25cf21..094fca9 100644 --- a/src/reward/dtos/update-reward.dto.ts +++ b/src/reward/dtos/update-reward.dto.ts @@ -23,7 +23,7 @@ export class UpdateRewardDto { @ApiPropertyOptional({ description: 'description of reward (optional)', type: String, - example: 'get 15% of in next corse', + example: 'get 15% of in next course', }) description?: string; @@ -34,7 +34,7 @@ export class UpdateRewardDto { type: String, example: 'url.png', }) - thumnail?: string; + thumbnail?: string; @IsEnum(Type, { message: `Invalid type. Type should be ${Type.BADGE} ${Type.CERTIFICATE} or ${Type.ITEM}`, diff --git a/src/reward/reward.controllers.ts b/src/reward/reward.controllers.ts index a16ad4e..86ffcf4 100644 --- a/src/reward/reward.controllers.ts +++ b/src/reward/reward.controllers.ts @@ -10,38 +10,80 @@ import { Post, Patch, Delete, + Query, + UploadedFile, + ParseFilePipeBuilder, + StreamableFile, + UseInterceptors, + Req, } from '@nestjs/common'; -import { ApiBearerAuth, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { + ApiBearerAuth, + ApiParam, + ApiResponse, + ApiTags, + ApiQuery, + ApiConsumes, + ApiBody, +} from '@nestjs/swagger'; import { RewardService } from './reward.service'; import { Roles } from 'src/shared/decorators/role.decorator'; -import { RewardResponseDto } from './dtos/reward-response.dto'; +import { + PaginatedRewardResponseDto, + RewardResponseDto, +} from './dtos/reward-response.dto'; import { CreateRewardDto } from './dtos/create-reward.dto'; import { Role } from 'src/shared/enums/roles.enum'; import { UpdateRewardDto } from './dtos/update-reward.dto'; -import { Public } from 'src/shared/decorators/public.decorator'; -import { Reward } from './reward.entity'; +import { FileService } from 'src/file/file.service'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { Folder } from 'src/file/enums/folder.enum'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; @Controller('reward') @Injectable() @ApiTags('Reward') +@ApiBearerAuth() export class RewardController { - constructor(private readonly rewardService: RewardService) {} + constructor( + private readonly rewardService: RewardService, + private readonly fileService: FileService, + ) {} @Get() - @Public() @ApiResponse({ status: HttpStatus.OK, - type: RewardResponseDto, + type: PaginatedRewardResponseDto, description: 'get all reward', isArray: true, }) - async findAll(): Promise { - const rewards = await this.rewardService.findAll(); - return rewards.map((reward) => new RewardResponseDto(reward)); + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by name', + }) + async findAll( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return this.rewardService.findAll(query, request.user.role); } @Get(':id') - @Public() @ApiResponse({ status: HttpStatus.OK, type: RewardResponseDto, @@ -56,19 +98,19 @@ export class RewardController { }), ) id: string, + @Req() request: AuthenticatedRequest, ): Promise { - const reward = await this.rewardService.findOne({ where: { id } }); + const reward = await this.rewardService.findOne(id, request.user.role); return new RewardResponseDto(reward); } @Post() @HttpCode(HttpStatus.CREATED) @Roles(Role.ADMIN) - @ApiBearerAuth() @ApiResponse({ status: HttpStatus.OK, type: RewardResponseDto, - description: 'create reward', + description: 'create new reward', }) async create( @Body() CreateRewardDto: CreateRewardDto, @@ -76,9 +118,84 @@ export class RewardController { return this.rewardService.create(CreateRewardDto); } + @Get('thumbnail/:id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Get thumbnail reward', + type: StreamableFile, + }) + async getThumbnail( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @Req() request: AuthenticatedRequest, + ): Promise { + const reward = await this.rewardService.findOne(id, request.user.role); + const file = await this.fileService.get(Folder.PROFILES, id); + return new StreamableFile(file, { + disposition: 'inline', + type: `image/${reward.thumbnail.split('.').pop()}`, + }); + } + + @Patch('thumbnail/:id') + @Roles(Role.ADMIN) + @UseInterceptors(FileInterceptor('file')) + @HttpCode(HttpStatus.NO_CONTENT) + @ApiResponse({ + description: 'upload file thumbnail of reward', + status: HttpStatus.NO_CONTENT, + }) + @ApiConsumes('multipart/form-data') + @ApiBody({ + schema: { + type: 'object', + properties: { + file: { + type: 'string', + format: 'binary', + }, + }, + }, + }) + async uploadThumbnail( + @UploadedFile( + new ParseFilePipeBuilder() + .addFileTypeValidator({ fileType: 'image/*' }) + .build({ + fileIsRequired: false, + errorHttpStatusCode: HttpStatus.BAD_REQUEST, + }), + ) + file: Express.Multer.File, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @Req() request: AuthenticatedRequest, + ): Promise { + const reward = await this.rewardService.findOne(id, request.user.role); + if (reward.thumbnail) + await this.fileService.update(Folder.REWARD_THUMBNAILS, id, file); + else { + await this.fileService.upload(Folder.REWARD_THUMBNAILS, id, file); + await this.rewardService.update(id, { + thumbnail: `${id}.${file.mimetype.split('/').pop()}`, + }); + } + } + @Patch(':id') @Roles(Role.ADMIN) - @ApiBearerAuth() @ApiResponse({ status: HttpStatus.OK, type: RewardResponseDto, @@ -101,9 +218,8 @@ export class RewardController { @Delete(':id') @Roles(Role.ADMIN) - @ApiBearerAuth() @ApiResponse({ - status: HttpStatus.OK, + status: HttpStatus.NO_CONTENT, description: 'delete reward', }) async delete( @@ -115,8 +231,7 @@ export class RewardController { }), ) id: string, - ): Promise<{ message: string }> { + ): Promise { await this.rewardService.delete(id); - return { message: 'Reward delete successfully' }; } } diff --git a/src/reward/reward.entity.ts b/src/reward/reward.entity.ts index 28adaab..5878383 100644 --- a/src/reward/reward.entity.ts +++ b/src/reward/reward.entity.ts @@ -9,7 +9,7 @@ import { } from 'typeorm'; import { Type } from './enums/type.enum'; import { Status } from './enums/status.enum'; -import { UserReward } from 'src/userReward/user-reward.entity'; +import { UserReward } from 'src/user-reward/user-reward.entity'; @Entity() export class Reward { diff --git a/src/reward/reward.module.ts b/src/reward/reward.module.ts index dfb1af0..23f9772 100644 --- a/src/reward/reward.module.ts +++ b/src/reward/reward.module.ts @@ -3,9 +3,12 @@ import { DatabaseModule } from 'src/database/database.module'; import { RewardController } from './reward.controllers'; import { rewardProviders } from './reward.providers'; import { RewardService } from './reward.service'; +import { FileModule } from 'src/file/file.module'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Reward } from './reward.entity'; @Module({ - imports: [DatabaseModule], + imports: [DatabaseModule, TypeOrmModule.forFeature([Reward]), FileModule], controllers: [RewardController], providers: [...rewardProviders, RewardService], exports: [RewardService], diff --git a/src/reward/reward.service.ts b/src/reward/reward.service.ts index 46eb39a..8a5a0cd 100644 --- a/src/reward/reward.service.ts +++ b/src/reward/reward.service.ts @@ -4,24 +4,56 @@ import { Injectable, NotFoundException, } from '@nestjs/common'; -import { FindOneOptions, Repository } from 'typeorm'; +import { FindOneOptions, ILike, Repository, FindOptionsWhere } from 'typeorm'; import { Reward } from './reward.entity'; import { CreateRewardDto } from './dtos/create-reward.dto'; import { UpdateRewardDto } from './dtos/update-reward.dto'; +import { ConfigService } from '@nestjs/config'; +import { createPagination } from 'src/shared/pagination'; +import { PaginatedRewardResponseDto } from './dtos/reward-response.dto'; +import { Status } from './enums/status.enum'; +import { Role } from 'src/shared/enums'; @Injectable() export class RewardService { constructor( @Inject('RewardRepository') private readonly rewardRepository: Repository, + private readonly configService: ConfigService, ) {} - async findAll(): Promise { - return this.rewardRepository.find(); + async findAll( + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + role?: Role, + ): Promise { + const { find } = await createPagination(this.rewardRepository, { + page, + limit, + }); + if (role && role === Role.ADMIN) { + const rewards = await find({ + where: { name: ILike(`%${search}%`) }, + }).run(); + return rewards; + } + const rewards = await find({ + where: { name: ILike(`%${search}%`), status: Status.ACTIVE }, + }).run(); + return rewards; } - async findOne(options: FindOneOptions): Promise { - const reward = await this.rewardRepository.findOne(options); + async findOne(id: string, role: Role): Promise { + const condition = + role === Role.ADMIN ? { id } : { id, status: Status.ACTIVE }; + const reward = await this.rewardRepository.findOne({ where: condition }); if (!reward) throw new NotFoundException('reward not found'); return reward; } diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index 6ef4ea9..68004b2 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -20,7 +20,7 @@ import { UserBackground } from 'src/user-background/user-background.entity'; import { UserOccupation } from 'src/user-occupation/user-occupation.entity'; import { UserStreak } from 'src/user-streak/user-streak.entity'; import { User } from 'src/user/user.entity'; -import { UserReward } from 'src/userReward/user-reward.entity'; +import { UserReward } from 'src/user-reward/user-reward.entity'; import { DataSource, DataSourceOptions } from 'typeorm'; import { GLOBAL_CONFIG } from '../constants/global-config.constant'; diff --git a/src/userReward/dtos/update-status-user-reward.dto.ts b/src/user-reward/dtos/update-status-user-reward.dto.ts similarity index 100% rename from src/userReward/dtos/update-status-user-reward.dto.ts rename to src/user-reward/dtos/update-status-user-reward.dto.ts diff --git a/src/userReward/dtos/update-user-reward.dto.ts b/src/user-reward/dtos/update-user-reward.dto.ts similarity index 100% rename from src/userReward/dtos/update-user-reward.dto.ts rename to src/user-reward/dtos/update-user-reward.dto.ts diff --git a/src/userReward/dtos/user-reward-response.dto.ts b/src/user-reward/dtos/user-reward-response.dto.ts similarity index 100% rename from src/userReward/dtos/user-reward-response.dto.ts rename to src/user-reward/dtos/user-reward-response.dto.ts diff --git a/src/userReward/enums/user-reward-status.enum.ts b/src/user-reward/enums/user-reward-status.enum.ts similarity index 100% rename from src/userReward/enums/user-reward-status.enum.ts rename to src/user-reward/enums/user-reward-status.enum.ts diff --git a/src/userReward/user-reward.controllers.ts b/src/user-reward/user-reward.controllers.ts similarity index 97% rename from src/userReward/user-reward.controllers.ts rename to src/user-reward/user-reward.controllers.ts index bdb64d0..87a37d1 100644 --- a/src/userReward/user-reward.controllers.ts +++ b/src/user-reward/user-reward.controllers.ts @@ -10,6 +10,7 @@ import { Req, Patch, Delete, + HttpCode, } from '@nestjs/common'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { Roles } from 'src/shared/decorators/role.decorator'; @@ -42,8 +43,9 @@ export class UserRewardController { @Post(':rewardId') @Roles(Role.STUDENT) + @HttpCode(HttpStatus.CREATED) @ApiResponse({ - status: HttpStatus.OK, + status: HttpStatus.CREATED, type: UserRewardResponseDto, description: 'create new user-reward', }) diff --git a/src/userReward/user-reward.entity.ts b/src/user-reward/user-reward.entity.ts similarity index 100% rename from src/userReward/user-reward.entity.ts rename to src/user-reward/user-reward.entity.ts diff --git a/src/userReward/user-reward.module.ts b/src/user-reward/user-reward.module.ts similarity index 100% rename from src/userReward/user-reward.module.ts rename to src/user-reward/user-reward.module.ts diff --git a/src/userReward/user-reward.providers.ts b/src/user-reward/user-reward.providers.ts similarity index 100% rename from src/userReward/user-reward.providers.ts rename to src/user-reward/user-reward.providers.ts diff --git a/src/userReward/user-reward.service.ts b/src/user-reward/user-reward.service.ts similarity index 100% rename from src/userReward/user-reward.service.ts rename to src/user-reward/user-reward.service.ts diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index a6fe352..55f2ad9 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -4,7 +4,7 @@ import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; import { Roadmap } from 'src/roadmap/roadmap.entity'; import { Role } from 'src/shared/enums/roles.enum'; import { UserBackground } from 'src/user-background/user-background.entity'; -import { UserReward } from 'src/userReward/user-reward.entity'; +import { UserReward } from 'src/user-reward/user-reward.entity'; import { Column, CreateDateColumn, From f956f4829a4797267ffd4a14f5f48bfe4136a136 Mon Sep 17 00:00:00 2001 From: Potsawee Date: Wed, 20 Nov 2024 15:52:41 +0700 Subject: [PATCH 092/155] add pagination --- src/category/category.controller.ts | 51 +++++++++++++++++++--- src/category/category.service.ts | 31 +++++++++++-- src/category/dtos/category-response.dto.ts | 17 ++++++++ src/exam/dtos/paginate-query-slug.dto.ts | 21 +++++++++ 4 files changed, 111 insertions(+), 9 deletions(-) create mode 100644 src/exam/dtos/paginate-query-slug.dto.ts diff --git a/src/category/category.controller.ts b/src/category/category.controller.ts index 175c85e..47d1815 100644 --- a/src/category/category.controller.ts +++ b/src/category/category.controller.ts @@ -9,16 +9,24 @@ import { Post, Injectable, Get, + Query, + HttpCode, } from '@nestjs/common'; import { CategoryService } from './category.service'; -import { categoryResponseDto } from './dtos/category-response.dto'; +import { + categoryResponseDto, + PaginatedCategoryDto, +} from './dtos/category-response.dto'; import { CreateCategoryDto } from './dtos/create-cateory.dto'; import { updateCategoryDto } from './dtos/update-category.dto'; import { Public } from 'src/shared/decorators/public.decorator'; -import { ApiBearerAuth, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiResponse, ApiTags, ApiQuery } from '@nestjs/swagger'; import { Category } from './category.entity'; import { Roles } from 'src/shared/decorators/role.decorator'; import { Role } from 'src/shared/enums'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { Slug } from './enums/slug.enum'; +import { PaginateCategoryQueryDto } from 'src/exam/dtos/paginate-query-slug.dto'; @Controller('category') @Injectable() @@ -28,8 +36,9 @@ export class CategoryController { @Post() @Roles(Role.ADMIN) + @HttpCode(HttpStatus.CREATED) @ApiResponse({ - status: HttpStatus.OK, + status: HttpStatus.CREATED, type: categoryResponseDto, description: 'create category', }) @@ -48,9 +57,39 @@ export class CategoryController { isArray: true, description: 'get all categories', }) - async findAll(): Promise { - const categories = await this.categoryService.findAll(); - return categories.map((category) => new categoryResponseDto(category)); + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + @ApiQuery({ + name: 'slug', + type: String, + required: false, + description: `search by slug (${Slug.COURSE} or ${Slug.REWARD})`, + }) + async findAll( + @Query() query: PaginateCategoryQueryDto, + ): Promise { + return await this.categoryService.findAll({ + page: query.page, + limit: query.limit, + search: query.search, + slug: query.slug, + }); } @Get(':id') diff --git a/src/category/category.service.ts b/src/category/category.service.ts index 65e7704..c8c3c75 100644 --- a/src/category/category.service.ts +++ b/src/category/category.service.ts @@ -6,8 +6,11 @@ import { } from '@nestjs/common'; import { CreateCategoryDto } from './dtos/create-cateory.dto'; import { Category } from './category.entity'; -import { FindOneOptions, FindOptionsWhere, Repository } from 'typeorm'; +import { FindOneOptions, FindOptionsWhere, ILike, Repository } from 'typeorm'; import { updateCategoryDto } from './dtos/update-category.dto'; +import { Slug } from './enums/slug.enum'; +import { createPagination } from 'src/shared/pagination'; +import { PaginatedCategoryDto } from './dtos/category-response.dto'; @Injectable() export class CategoryService { @@ -16,8 +19,30 @@ export class CategoryService { private readonly categoryRepository: Repository, ) {} - async findAll(): Promise { - return this.categoryRepository.find(); + async findAll({ + page = 1, + limit = 20, + search = '', + slug = '', + }: { + page?: number; + limit?: number; + search?: string; + slug?: Slug | ''; + }): Promise { + const { find } = await createPagination(this.categoryRepository, { + page, + limit, + }); + const conditionSearch = search ? { title: ILike(`%${search}%`) } : {}; + const connditionSlug = slug ? { slug: slug } : {}; + const categories = await find({ + where: { + ...conditionSearch, + ...connditionSlug, + }, + }).run(); + return categories; } async findOne(options: FindOneOptions): Promise { diff --git a/src/category/dtos/category-response.dto.ts b/src/category/dtos/category-response.dto.ts index 7886d24..020230f 100644 --- a/src/category/dtos/category-response.dto.ts +++ b/src/category/dtos/category-response.dto.ts @@ -1,6 +1,7 @@ import { Slug } from 'src/category/enums/slug.enum'; import { Category } from '../category.entity'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; export class categoryResponseDto { @ApiProperty({ @@ -55,3 +56,19 @@ export class categoryResponseDto { this.updatedAt = category.updatedAt; } } + +export class PaginatedCategoryDto extends PaginatedResponse( + categoryResponseDto, +) { + constructor( + categories: Category[], + total: number, + pageSize: number, + currentPage: number, + ) { + const categoryDtos = categories.map( + (category) => new categoryResponseDto(category), + ); + super(categoryDtos, total, pageSize, currentPage); + } +} diff --git a/src/exam/dtos/paginate-query-slug.dto.ts b/src/exam/dtos/paginate-query-slug.dto.ts new file mode 100644 index 0000000..cb65d8e --- /dev/null +++ b/src/exam/dtos/paginate-query-slug.dto.ts @@ -0,0 +1,21 @@ +import { ApiProperty, IntersectionType } from '@nestjs/swagger'; +import { IsEnum, IsOptional } from 'class-validator'; +import { Slug } from 'src/category/enums/slug.enum'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; + +class SlugQueryDto { + @ApiProperty({ + name: 'slug', + enum: Slug, + required: false, + description: `sort by slug (${Slug.COURSE} or ${Slug.REWARD})`, + }) + @IsOptional() + @IsEnum(Slug, { message: `slug must be ${Slug.COURSE} or ${Slug.REWARD}` }) + slug: Slug; +} + +export class PaginateCategoryQueryDto extends IntersectionType( + PaginateQueryDto, + SlugQueryDto, +) {} From 1d7d6bf415976cdbc692d9960efcd845a5f242d0 Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Thu, 21 Nov 2024 00:07:19 +0700 Subject: [PATCH 093/155] feat(course): enable thumbnail upload and retrieval for courses, refactor entity, service, and dtos accordingly --- src/course/course.controller.ts | 110 ++++++++++++++++++++++++++- src/course/course.entity.ts | 2 +- src/course/course.module.ts | 3 +- src/course/course.service.ts | 9 ++- src/course/dtos/create-course.dto.ts | 8 -- src/course/dtos/update-course.dto.ts | 16 +++- 6 files changed, 133 insertions(+), 15 deletions(-) diff --git a/src/course/course.controller.ts b/src/course/course.controller.ts index 68dd773..417f712 100644 --- a/src/course/course.controller.ts +++ b/src/course/course.controller.ts @@ -4,17 +4,24 @@ import { Controller, Delete, Get, + HttpCode, HttpStatus, Injectable, Param, + ParseFilePipeBuilder, ParseUUIDPipe, Patch, Post, Query, Req, + StreamableFile, + UploadedFile, + UseInterceptors, } from '@nestjs/common'; import { ApiBearerAuth, + ApiBody, + ApiConsumes, ApiParam, ApiQuery, ApiResponse, @@ -33,7 +40,12 @@ import { UpdateCourseDto, } from './dtos/index'; import { CourseOwnership } from 'src/shared/decorators/course-ownership.decorator'; -import { CourseOwnershipGuard } from 'src/shared/guards/course-ownership.guard'; +import { Public } from 'src/shared/decorators/public.decorator'; +import { FileService } from 'src/file/file.service'; +import { Folder } from 'src/file/enums/folder.enum'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { ConfigService } from '@nestjs/config'; +import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; @Controller('course') @ApiTags('Course') @@ -43,7 +55,101 @@ export class CourseController { constructor( private readonly courseService: CourseService, private readonly categoryService: CategoryService, + private readonly fileService: FileService, + private readonly configService: ConfigService, ) {} + + @Get(':id/thumbnail') + @Public() + @ApiParam({ + name: 'id', + type: String, + description: 'Course id', + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Get course thumbnail', + type: StreamableFile, + }) + async getThumbnail( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const course = await this.courseService.findOne({ + where: { id }, + }); + const file = await this.fileService.get(Folder.COURSE_THUMBNAILS, course.thumbnail); + return new StreamableFile(file, { + disposition: 'inline', + type: `image/${course.thumbnail.split('.').pop()}`, + }); + } + + + + @Patch(':id/thumbnail') + @CourseOwnership({ adminDraftOnly: true }) + @ApiBearerAuth() + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Thumbnail updated successfully', + }) + @HttpCode(HttpStatus.NO_CONTENT) + @UseInterceptors(FileInterceptor('file')) + @ApiConsumes('multipart/form-data') + @ApiBody({ + schema: { + type: 'object', + properties: { + file: { + type: 'string', + format: 'binary', + }, + }, + }, + }) + @ApiParam({ + name: 'id', + type: String, + description: 'Course id', + }) + async uploadThumbnail( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @UploadedFile( + new ParseFilePipeBuilder() + .addFileTypeValidator({ fileType: 'image/*' }) + .build({ + fileIsRequired: true, + errorHttpStatusCode: HttpStatus.BAD_REQUEST, + }), + ) + file: Express.Multer.File, + ): Promise { + const course = await this.courseService.findOne({ + where: { id }, + }); + if (course.thumbnail) + await this.fileService.update(Folder.COURSE_THUMBNAILS, id, file); + else { + await this.fileService.upload(Folder.COURSE_THUMBNAILS, id, file); + } + await this.courseService.update(id, { thumbnail: `${id}.${file.mimetype.split('/').pop()}` }); + } + + @Get() @ApiResponse({ status: HttpStatus.OK, @@ -104,7 +210,7 @@ export class CourseController { ) id: string, ): Promise { - const course = await this.courseService.findOne( + const course = await this.courseService.findOneWithOwnership( request.user.id, request.user.role, { where: { id } }, diff --git a/src/course/course.entity.ts b/src/course/course.entity.ts index 240dbc5..0ee2724 100644 --- a/src/course/course.entity.ts +++ b/src/course/course.entity.ts @@ -54,7 +54,7 @@ export class Course { @Column({ type: String, - nullable: false, + nullable: true, }) thumbnail: string; diff --git a/src/course/course.module.ts b/src/course/course.module.ts index 2b26e35..0ed9c1a 100644 --- a/src/course/course.module.ts +++ b/src/course/course.module.ts @@ -6,9 +6,10 @@ import { courseProviders } from './course.provider'; import { CourseService } from './course.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Course } from './course.entity'; +import { FileModule } from 'src/file/file.module'; @Module({ - imports: [DatabaseModule, CategoryModule, TypeOrmModule.forFeature([Course])], + imports: [DatabaseModule, CategoryModule, TypeOrmModule.forFeature([Course]), FileModule], controllers: [CourseController], providers: [...courseProviders, CourseService], exports: [CourseService], diff --git a/src/course/course.service.ts b/src/course/course.service.ts index d27d128..492722a 100644 --- a/src/course/course.service.ts +++ b/src/course/course.service.ts @@ -53,7 +53,14 @@ export class CourseService { return courses; } - async findOne( + async findOne(options: FindOneOptions): Promise { + const course = await this.courseRepository.findOne(options); + if (!course) throw new NotFoundException('Course not found'); + return course; + } + + + async findOneWithOwnership( userId: string, role: Role, options: FindOneOptions, diff --git a/src/course/dtos/create-course.dto.ts b/src/course/dtos/create-course.dto.ts index 4a7bd2c..3d43a4f 100644 --- a/src/course/dtos/create-course.dto.ts +++ b/src/course/dtos/create-course.dto.ts @@ -26,14 +26,6 @@ export class CreateCourseDto { }) categoryId: string; - @IsNotEmpty() - @ApiProperty({ - description: 'Course thumbnail', - type: String, - example: 'https://www.example.com/thumbnail.jpg', - }) - thumbnail: string; - @IsNotEmpty() @ApiProperty({ description: 'Course duration', diff --git a/src/course/dtos/update-course.dto.ts b/src/course/dtos/update-course.dto.ts index 3fdbb91..76d9ff8 100644 --- a/src/course/dtos/update-course.dto.ts +++ b/src/course/dtos/update-course.dto.ts @@ -1,4 +1,16 @@ -import { PartialType } from '@nestjs/swagger'; +import { ApiProperty, PartialType } from '@nestjs/swagger'; import { CreateCourseDto } from './index'; +import { IsOptional, IsString } from 'class-validator'; -export class UpdateCourseDto extends PartialType(CreateCourseDto) {} +export class UpdateCourseDto extends PartialType(CreateCourseDto) { + + @IsOptional() + @IsString() + @ApiProperty({ + description: 'Course thumbnail', + type: String, + example: 'https://www.example.com/thumbnail.jpg', + }) + thumbnail: string; + +} From 96b661441c034edac4ba034279fd67d7858758dc Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Thu, 21 Nov 2024 00:15:25 +0700 Subject: [PATCH 094/155] refactor(course): change thumbnail to thumbnailKey in course entity, controller, and response DTO for consistency in handling files --- src/course/course.controller.ts | 7 +++---- src/course/course.entity.ts | 2 +- src/course/dtos/course-response.dto.ts | 7 ------- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/src/course/course.controller.ts b/src/course/course.controller.ts index 417f712..fb995ea 100644 --- a/src/course/course.controller.ts +++ b/src/course/course.controller.ts @@ -45,7 +45,6 @@ import { FileService } from 'src/file/file.service'; import { Folder } from 'src/file/enums/folder.enum'; import { FileInterceptor } from '@nestjs/platform-express'; import { ConfigService } from '@nestjs/config'; -import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; @Controller('course') @ApiTags('Course') @@ -84,10 +83,10 @@ export class CourseController { const course = await this.courseService.findOne({ where: { id }, }); - const file = await this.fileService.get(Folder.COURSE_THUMBNAILS, course.thumbnail); + const file = await this.fileService.get(Folder.COURSE_THUMBNAILS, course.thumbnailKey); return new StreamableFile(file, { disposition: 'inline', - type: `image/${course.thumbnail.split('.').pop()}`, + type: `image/${course.thumbnailKey.split('.').pop()}`, }); } @@ -141,7 +140,7 @@ export class CourseController { const course = await this.courseService.findOne({ where: { id }, }); - if (course.thumbnail) + if (course.thumbnailKey) await this.fileService.update(Folder.COURSE_THUMBNAILS, id, file); else { await this.fileService.upload(Folder.COURSE_THUMBNAILS, id, file); diff --git a/src/course/course.entity.ts b/src/course/course.entity.ts index 0ee2724..a4765d5 100644 --- a/src/course/course.entity.ts +++ b/src/course/course.entity.ts @@ -56,7 +56,7 @@ export class Course { type: String, nullable: true, }) - thumbnail: string; + thumbnailKey: string; @Column({ type: Number, diff --git a/src/course/dtos/course-response.dto.ts b/src/course/dtos/course-response.dto.ts index 1727466..a11c986 100644 --- a/src/course/dtos/course-response.dto.ts +++ b/src/course/dtos/course-response.dto.ts @@ -55,12 +55,6 @@ export class CourseResponseDto { }) category: categoryResponseDto; - @ApiProperty({ - description: 'Course thumbnail', - type: String, - example: 'https://www.example.com/thumbnail.jpg', - }) - thumbnail: string; @ApiProperty({ description: 'Course duration', @@ -111,7 +105,6 @@ export class CourseResponseDto { this.title = course.title; this.description = course.description; this.teacher = new UserResponseDto(course.teacher); - this.thumbnail = course.thumbnail; this.category = new categoryResponseDto(course.category); this.duration = course.duration; this.level = course.level; From 5a4c049105e875123a4cc4e2054d7d355afd9cb8 Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Thu, 21 Nov 2024 00:22:38 +0700 Subject: [PATCH 095/155] refactor(course): rename thumbnail to thumbnailKey in controller and update DTO to standardize file handling across the application --- src/course/course.controller.ts | 4 +--- src/course/dtos/update-course.dto.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/course/course.controller.ts b/src/course/course.controller.ts index fb995ea..199a2fb 100644 --- a/src/course/course.controller.ts +++ b/src/course/course.controller.ts @@ -44,7 +44,6 @@ import { Public } from 'src/shared/decorators/public.decorator'; import { FileService } from 'src/file/file.service'; import { Folder } from 'src/file/enums/folder.enum'; import { FileInterceptor } from '@nestjs/platform-express'; -import { ConfigService } from '@nestjs/config'; @Controller('course') @ApiTags('Course') @@ -55,7 +54,6 @@ export class CourseController { private readonly courseService: CourseService, private readonly categoryService: CategoryService, private readonly fileService: FileService, - private readonly configService: ConfigService, ) {} @Get(':id/thumbnail') @@ -145,7 +143,7 @@ export class CourseController { else { await this.fileService.upload(Folder.COURSE_THUMBNAILS, id, file); } - await this.courseService.update(id, { thumbnail: `${id}.${file.mimetype.split('/').pop()}` }); + await this.courseService.update(id, { thumbnailKey: `${id}.${file.mimetype.split('/').pop()}` }); } diff --git a/src/course/dtos/update-course.dto.ts b/src/course/dtos/update-course.dto.ts index 76d9ff8..dbfe360 100644 --- a/src/course/dtos/update-course.dto.ts +++ b/src/course/dtos/update-course.dto.ts @@ -11,6 +11,6 @@ export class UpdateCourseDto extends PartialType(CreateCourseDto) { type: String, example: 'https://www.example.com/thumbnail.jpg', }) - thumbnail: string; + thumbnailKey: string; } From 3ced5d4634030c1cfb0762f4db369cdb0aa456f7 Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Thu, 21 Nov 2024 00:46:40 +0700 Subject: [PATCH 096/155] fix(course): update thumbnail update logic to use correct keys in course and file services for consistent file handling --- src/course/course.controller.ts | 4 ++-- src/file/file.service.ts | 2 +- src/user/user.controller.ts | 6 ++---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/course/course.controller.ts b/src/course/course.controller.ts index 199a2fb..ebe715a 100644 --- a/src/course/course.controller.ts +++ b/src/course/course.controller.ts @@ -139,11 +139,11 @@ export class CourseController { where: { id }, }); if (course.thumbnailKey) - await this.fileService.update(Folder.COURSE_THUMBNAILS, id, file); + await this.fileService.update(Folder.COURSE_THUMBNAILS, course.thumbnailKey, file); else { await this.fileService.upload(Folder.COURSE_THUMBNAILS, id, file); } - await this.courseService.update(id, { thumbnailKey: `${id}.${file.mimetype.split('/').pop()}` }); + await this.courseService.update(id, { thumbnailKey: `${id}.${file.originalname.split('.').pop()}` }); } diff --git a/src/file/file.service.ts b/src/file/file.service.ts index f5f6d11..714aafc 100644 --- a/src/file/file.service.ts +++ b/src/file/file.service.ts @@ -81,6 +81,6 @@ export class FileService { file: Express.Multer.File, ): Promise { await this.delete(folder, key); - await this.upload(folder, key, file); + await this.upload(folder, key.split('.')[0], file); } } diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 9bfb668..979a3ee 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -116,13 +116,11 @@ export class UserController { where: { id: request.user.id }, }); if (user.profileKey) - await this.fileService.update(Folder.PROFILES, user.id, file); + await this.fileService.update(Folder.PROFILES, user.profileKey, file); else { await this.fileService.upload(Folder.PROFILES, user.id, file); - await this.userService.update(user.id, { - profileKey: `${user.id}.${file.mimetype.split('/').pop()}`, - }); } + await this.userService.update(request.user.id, { profileKey: `${user.id}.${file.originalname.split('.').pop()}` }); } @Get('avatar') From a30d3b9a4a3cea7fc4718f7223b306e82123257e Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Thu, 21 Nov 2024 00:50:45 +0700 Subject: [PATCH 097/155] refactor: Update Chapter management for video handling, integrating FileService and changing videoUrl to videoKey --- src/chapter/chapter.controller.ts | 8 +++++++- src/chapter/chapter.entity.ts | 4 ++-- src/chapter/chapter.module.ts | 2 ++ src/chapter/chapter.service.ts | 9 ++++++++- src/chapter/dtos/chapter-response.dto.ts | 8 -------- src/chapter/dtos/create-chapter.dto.ts | 8 -------- src/chapter/dtos/update-chapter.dto.ts | 12 +++++++++++- src/file/file.service.ts | 2 +- 8 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/chapter/chapter.controller.ts b/src/chapter/chapter.controller.ts index fb3b1b2..d81419f 100644 --- a/src/chapter/chapter.controller.ts +++ b/src/chapter/chapter.controller.ts @@ -36,6 +36,7 @@ import { CourseOwnership } from 'src/shared/decorators/course-ownership.decorato import { CourseModuleService } from 'src/course-module/course-module.service'; import { ChatRoomResponseDto } from 'src/chat-room/dtos'; import { ChatRoomService } from 'src/chat-room/chat-room.service'; +import { FileService } from 'src/file/file.service'; @Controller('chapter') @ApiTags('Chapters') @@ -45,8 +46,13 @@ export class ChapterController { constructor( private readonly chapterService: ChapterService, private readonly courseModuleService: CourseModuleService, + private readonly fileService: FileService, ) {} + @Get(':id/video') + + + @Get() @ApiResponse({ status: HttpStatus.OK, @@ -118,7 +124,7 @@ export class ChapterController { @Req() request: AuthenticatedRequest, @Param('id', ParseUUIDPipe) id: string, ): Promise { - return this.chapterService.findOne(request.user.id, request.user.role, { + return this.chapterService.findOneWithOwnership(request.user.id, request.user.role, { where: { id }, }); } diff --git a/src/chapter/chapter.entity.ts b/src/chapter/chapter.entity.ts index 97ed6e4..4ab7942 100644 --- a/src/chapter/chapter.entity.ts +++ b/src/chapter/chapter.entity.ts @@ -46,9 +46,9 @@ export class Chapter { @Column({ type: String, - nullable: false, + nullable: true, }) - videoUrl: string; + videoKey: string; @Column({ type: String, diff --git a/src/chapter/chapter.module.ts b/src/chapter/chapter.module.ts index e990b62..8c81889 100644 --- a/src/chapter/chapter.module.ts +++ b/src/chapter/chapter.module.ts @@ -9,6 +9,7 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { CourseModule } from 'src/course-module/course-module.entity'; import { ChatRoomModule } from 'src/chat-room/chat-room.module'; import { EnrollmentModule } from 'src/enrollment/enrollment.module'; +import { FileModule } from 'src/file/file.module'; @Module({ imports: [ @@ -17,6 +18,7 @@ import { EnrollmentModule } from 'src/enrollment/enrollment.module'; TypeOrmModule.forFeature([Chapter, CourseModule]), ChatRoomModule, EnrollmentModule, + FileModule ], controllers: [ChapterController], providers: [...chapterProviders, ChapterService], diff --git a/src/chapter/chapter.service.ts b/src/chapter/chapter.service.ts index dce34a8..a4485cb 100644 --- a/src/chapter/chapter.service.ts +++ b/src/chapter/chapter.service.ts @@ -57,7 +57,14 @@ export class ChapterService { return chapters; } - async findOne( + async findOne(options: FindOneOptions): Promise { + const chapter = await this.chapterRepository.findOne(options); + if (!chapter) throw new NotFoundException('Chapter not found'); + return chapter; + } + + + async findOneWithOwnership( userId: string, role: Role, options: FindOneOptions, diff --git a/src/chapter/dtos/chapter-response.dto.ts b/src/chapter/dtos/chapter-response.dto.ts index 4ffe5fc..ef1132e 100644 --- a/src/chapter/dtos/chapter-response.dto.ts +++ b/src/chapter/dtos/chapter-response.dto.ts @@ -25,13 +25,6 @@ export class ChapterResponseDto { }) description: string; - @ApiProperty({ - description: 'Chapter video URL', - type: String, - example: 'https://www.youtube.com/watch?v=8k-9mU5KfBQ', - }) - videoUrl: string; - @ApiProperty({ description: 'Chapter content', type: String, @@ -84,7 +77,6 @@ export class ChapterResponseDto { this.id = chapter.id; this.title = chapter.title; this.description = chapter.description; - this.videoUrl = chapter.videoUrl; this.content = chapter.content; this.summary = chapter.summary; this.duration = chapter.duration; diff --git a/src/chapter/dtos/create-chapter.dto.ts b/src/chapter/dtos/create-chapter.dto.ts index d2c573f..b516661 100644 --- a/src/chapter/dtos/create-chapter.dto.ts +++ b/src/chapter/dtos/create-chapter.dto.ts @@ -26,14 +26,6 @@ export class CreateChapterDto { }) description: string; - @IsNotEmpty() - @IsString() - @ApiProperty({ - description: 'Chapter video URL', - type: String, - example: 'https://www.youtube.com/watch?v=8k-9mU5KfBQ', - }) - videoUrl: string; @IsNotEmpty() @IsString() diff --git a/src/chapter/dtos/update-chapter.dto.ts b/src/chapter/dtos/update-chapter.dto.ts index 24b09f4..993af82 100644 --- a/src/chapter/dtos/update-chapter.dto.ts +++ b/src/chapter/dtos/update-chapter.dto.ts @@ -1,6 +1,6 @@ import { PartialType } from '@nestjs/mapped-types'; import { CreateChapterDto } from './create-chapter.dto'; -import { IsNumber, IsOptional } from 'class-validator'; +import { IsNumber, IsOptional, IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; export class UpdateChapterDto extends PartialType(CreateChapterDto) { @@ -12,4 +12,14 @@ export class UpdateChapterDto extends PartialType(CreateChapterDto) { example: 1, }) orderIndex: number; + @IsOptional() + @IsString() + @ApiProperty({ + description: 'Chapter video key', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + videoKey: string; + + } diff --git a/src/file/file.service.ts b/src/file/file.service.ts index f5f6d11..714aafc 100644 --- a/src/file/file.service.ts +++ b/src/file/file.service.ts @@ -81,6 +81,6 @@ export class FileService { file: Express.Multer.File, ): Promise { await this.delete(folder, key); - await this.upload(folder, key, file); + await this.upload(folder, key.split('.')[0], file); } } From 662ff9926173267775840c90cb6f315319556fb1 Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Thu, 21 Nov 2024 02:02:20 +0700 Subject: [PATCH 098/155] feat(chapter): add video upload and retrieval endpoints, enhancing video management in ChapterController and refining service logic --- src/chapter/chapter.controller.ts | 102 +++++++++++++++++++++ src/chapter/chapter.service.ts | 32 ++++--- src/chapter/dtos/update-chapter.dto.ts | 4 +- src/course-module/course-module.service.ts | 9 +- src/course/course.service.ts | 9 +- src/course/dtos/update-course.dto.ts | 4 +- 6 files changed, 137 insertions(+), 23 deletions(-) diff --git a/src/chapter/chapter.controller.ts b/src/chapter/chapter.controller.ts index d81419f..0e61978 100644 --- a/src/chapter/chapter.controller.ts +++ b/src/chapter/chapter.controller.ts @@ -4,18 +4,25 @@ import { Controller, Delete, Get, + HttpCode, HttpStatus, Injectable, Param, + ParseFilePipeBuilder, ParseUUIDPipe, Patch, Post, Query, Req, + StreamableFile, + UploadedFile, UseGuards, + UseInterceptors, } from '@nestjs/common'; import { ApiBearerAuth, + ApiBody, + ApiConsumes, ApiParam, ApiQuery, ApiResponse, @@ -37,6 +44,9 @@ import { CourseModuleService } from 'src/course-module/course-module.service'; import { ChatRoomResponseDto } from 'src/chat-room/dtos'; import { ChatRoomService } from 'src/chat-room/chat-room.service'; import { FileService } from 'src/file/file.service'; +import { Folder } from 'src/file/enums/folder.enum'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { EnrollmentService } from 'src/enrollment/enrollment.service'; @Controller('chapter') @ApiTags('Chapters') @@ -47,9 +57,101 @@ export class ChapterController { private readonly chapterService: ChapterService, private readonly courseModuleService: CourseModuleService, private readonly fileService: FileService, + private readonly enrollmentService: EnrollmentService, ) {} @Get(':id/video') + @ApiParam({ + name: 'id', + type: String, + description: 'Course id', + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Get chapter video', + type: StreamableFile, + }) + async getVideo( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const chapter = await this.chapterService.findOneWithOwnership( + request.user.id, + request.user.role, + { where: { id } }, + ); + + const file = await this.fileService.get(Folder.CHAPTER_VIDEOS, chapter.videoKey); + return new StreamableFile(file, { + disposition: 'inline', + type: `video/${chapter.videoKey.split('.').pop()}`, + }); + } + + + + @Patch(':id/video') + @CourseOwnership({ adminDraftOnly: true }) + @ApiBearerAuth() + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Video updated successfully', + }) + @HttpCode(HttpStatus.NO_CONTENT) + @UseInterceptors(FileInterceptor('file')) + @ApiConsumes('multipart/form-data') + @ApiBody({ + schema: { + type: 'object', + properties: { + file: { + type: 'string', + format: 'binary', + }, + }, + }, + }) + @ApiParam({ + name: 'id', + type: String, + description: 'Chapter id', + }) + async uploadVideo( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @UploadedFile( + new ParseFilePipeBuilder() + .addFileTypeValidator({ fileType: 'video/*' }) + .build({ + fileIsRequired: true, + errorHttpStatusCode: HttpStatus.BAD_REQUEST, + }), + ) + file: Express.Multer.File, + ): Promise { + const chapter = await this.chapterService.findOne({ + where: { id }, + }); + if (chapter.videoKey) + await this.fileService.update(Folder.CHAPTER_VIDEOS, chapter.videoKey, file); + else { + await this.fileService.upload(Folder.CHAPTER_VIDEOS, id, file); + } + await this.chapterService.update(id, { videoKey: `${id}.${file.originalname.split('.').pop()}` }); + } diff --git a/src/chapter/chapter.service.ts b/src/chapter/chapter.service.ts index a4485cb..87e3786 100644 --- a/src/chapter/chapter.service.ts +++ b/src/chapter/chapter.service.ts @@ -6,7 +6,7 @@ import { } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { createPagination } from 'src/shared/pagination'; -import { FindOneOptions, FindOptionsWhere, ILike, Repository } from 'typeorm'; +import { FindOneOptions, FindOptionsWhere, ILike, Not, Repository } from 'typeorm'; import { Chapter } from './chapter.entity'; import { PaginatedChapterResponseDto } from './dtos/chapter-response.dto'; import { CreateChapterDto } from './dtos/create-chapter.dto'; @@ -16,6 +16,7 @@ import { ChatRoomService } from 'src/chat-room/chat-room.service'; import { ChatRoomStatus, ChatRoomType } from 'src/chat-room/enums'; import { EnrollmentService } from 'src/enrollment/enrollment.service'; import { ChatRoom } from 'src/chat-room/chat-room.entity'; +import { EnrollmentStatus } from 'src/enrollment/enums/enrollment-status.enum'; @Injectable() export class ChapterService { @@ -24,7 +25,7 @@ export class ChapterService { private readonly chapterRepository: Repository, private readonly chatRoomService: ChatRoomService, private readonly enrollmentService: EnrollmentService, - ) {} + ) { } async findAll({ page = 1, @@ -257,7 +258,7 @@ export class ChapterService { userId: string, role: Role, baseCondition: FindOptionsWhere = {}, - ) { + ): FindOptionsWhere | FindOptionsWhere[] { const conditions: Record< Role, () => FindOptionsWhere | FindOptionsWhere[] @@ -267,34 +268,35 @@ export class ChapterService { module: { course: { status: CourseStatus.PUBLISHED, - }, - }, + enrollments: { + user: { id: userId }, + status: Not(EnrollmentStatus.DROPPED) + } + } + } }), [Role.TEACHER]: () => [ { ...baseCondition, module: { course: { - status: CourseStatus.PUBLISHED, - }, - }, + status: CourseStatus.PUBLISHED + } + } }, { ...baseCondition, module: { course: { - teacher: { - id: userId, - }, - }, - }, - }, + teacher: { id: userId } + } + } + } ], [Role.ADMIN]: () => baseCondition, }; const buildCondition = conditions[role]; - if (!buildCondition) { throw new BadRequestException('Invalid role'); } diff --git a/src/chapter/dtos/update-chapter.dto.ts b/src/chapter/dtos/update-chapter.dto.ts index 993af82..154f6aa 100644 --- a/src/chapter/dtos/update-chapter.dto.ts +++ b/src/chapter/dtos/update-chapter.dto.ts @@ -11,7 +11,7 @@ export class UpdateChapterDto extends PartialType(CreateChapterDto) { type: Number, example: 1, }) - orderIndex: number; + orderIndex?: number; @IsOptional() @IsString() @ApiProperty({ @@ -19,7 +19,7 @@ export class UpdateChapterDto extends PartialType(CreateChapterDto) { type: String, example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', }) - videoKey: string; + videoKey?: string; } diff --git a/src/course-module/course-module.service.ts b/src/course-module/course-module.service.ts index ce0abc2..62f52c9 100644 --- a/src/course-module/course-module.service.ts +++ b/src/course-module/course-module.service.ts @@ -5,19 +5,20 @@ import { NotFoundException, } from '@nestjs/common'; import { createPagination } from 'src/shared/pagination'; -import { FindOneOptions, FindOptionsWhere, ILike, Repository } from 'typeorm'; +import { FindOneOptions, FindOptionsWhere, ILike, Not, Repository } from 'typeorm'; import { CourseModule } from './course-module.entity'; import { PaginatedCourseModuleResponseDto } from './dtos/course-module-response.dto'; import { CreateCourseModuleDto } from './dtos/create-course-module.dto'; import { UpdateCourseModuleDto } from './dtos/update-course-module.dto'; import { CourseStatus, Role } from 'src/shared/enums'; +import { EnrollmentStatus } from 'src/enrollment/enums/enrollment-status.enum'; @Injectable() export class CourseModuleService { constructor( @Inject('CourseModuleRepository') private readonly courseModuleRepository: Repository, - ) {} + ) { } async findAll({ page = 1, @@ -230,6 +231,10 @@ export class CourseModuleService { ...baseCondition, course: { status: CourseStatus.PUBLISHED, + enrollments: { + user: { id: userId }, + status: Not(EnrollmentStatus.DROPPED), + }, }, }), [Role.TEACHER]: () => [ diff --git a/src/course/course.service.ts b/src/course/course.service.ts index 492722a..45334cf 100644 --- a/src/course/course.service.ts +++ b/src/course/course.service.ts @@ -6,20 +6,21 @@ import { } from '@nestjs/common'; import { CourseStatus, Role } from 'src/shared/enums'; import { createPagination } from 'src/shared/pagination'; -import { FindOneOptions, FindOptionsWhere, ILike, Repository } from 'typeorm'; +import { FindOneOptions, FindOptionsWhere, ILike, Not, Repository } from 'typeorm'; import { Course } from './course.entity'; import { CreateCourseDto, PaginatedCourseResponeDto, UpdateCourseDto, } from './dtos/index'; +import { EnrollmentStatus } from 'src/enrollment/enums/enrollment-status.enum'; @Injectable() export class CourseService { constructor( @Inject('CourseRepository') private readonly courseRepository: Repository, - ) {} + ) { } async findAll({ page = 1, @@ -148,6 +149,10 @@ export class CourseService { [Role.STUDENT]: () => ({ ...baseCondition, status: CourseStatus.PUBLISHED, + enrollments: { + user: { id: userId }, + status: Not(EnrollmentStatus.DROPPED), + }, }), [Role.TEACHER]: () => [ { diff --git a/src/course/dtos/update-course.dto.ts b/src/course/dtos/update-course.dto.ts index dbfe360..8fa8ad0 100644 --- a/src/course/dtos/update-course.dto.ts +++ b/src/course/dtos/update-course.dto.ts @@ -7,9 +7,9 @@ export class UpdateCourseDto extends PartialType(CreateCourseDto) { @IsOptional() @IsString() @ApiProperty({ - description: 'Course thumbnail', + description: 'Course thumbnail Key', type: String, - example: 'https://www.example.com/thumbnail.jpg', + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', }) thumbnailKey: string; From b7c5746ede099d6d276c0dc9fd4584af79fe2931 Mon Sep 17 00:00:00 2001 From: Potsawee Date: Thu, 21 Nov 2024 12:02:00 +0700 Subject: [PATCH 099/155] add search by type --- src/reward/dtos/create-reward.dto.ts | 11 +--------- src/reward/dtos/paginate-reward-query.dto.ts | 23 ++++++++++++++++++++ src/reward/dtos/reward-response.dto.ts | 2 +- src/reward/reward.controllers.ts | 20 ++++++++++++----- src/reward/reward.entity.ts | 9 ++++++-- src/reward/reward.service.ts | 16 ++++++++++++-- 6 files changed, 60 insertions(+), 21 deletions(-) create mode 100644 src/reward/dtos/paginate-reward-query.dto.ts diff --git a/src/reward/dtos/create-reward.dto.ts b/src/reward/dtos/create-reward.dto.ts index 82b559b..9cf3967 100644 --- a/src/reward/dtos/create-reward.dto.ts +++ b/src/reward/dtos/create-reward.dto.ts @@ -24,19 +24,10 @@ export class CreateRewardDto { @ApiPropertyOptional({ description: 'description of reward (optional)', type: String, - example: 'get 15% of in next corse', + example: 'get 15% of in next course', }) description?: string; - @IsString() - @IsOptional() - @ApiPropertyOptional({ - description: 'thumbnail of reward (optional)', - type: String, - example: 'url.png', - }) - thumbnail: string; - @IsEnum(Type, { message: `Invalid type. Type should be ${Type.BADGE} ${Type.CERTIFICATE} or ${Type.ITEM}`, }) diff --git a/src/reward/dtos/paginate-reward-query.dto.ts b/src/reward/dtos/paginate-reward-query.dto.ts new file mode 100644 index 0000000..1b25acf --- /dev/null +++ b/src/reward/dtos/paginate-reward-query.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty, IntersectionType } from '@nestjs/swagger'; +import { Type } from '../enums/type.enum'; +import { IsEnum, IsOptional } from 'class-validator'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; + +class TypeQueryDto { + @ApiProperty({ + name: 'type', + enum: Type, + required: false, + description: `sort by type (${Type.BADGE} ${Type.CERTIFICATE} or ${Type.ITEM})`, + }) + @IsOptional() + @IsEnum(Type, { + message: `type must be ${Type.BADGE} ${Type.CERTIFICATE} or ${Type.ITEM}`, + }) + type: Type; +} + +export class PaginateRewardQueryDto extends IntersectionType( + PaginateQueryDto, + TypeQueryDto, +) {} diff --git a/src/reward/dtos/reward-response.dto.ts b/src/reward/dtos/reward-response.dto.ts index 7c17bf3..c7f8ddb 100644 --- a/src/reward/dtos/reward-response.dto.ts +++ b/src/reward/dtos/reward-response.dto.ts @@ -31,7 +31,7 @@ export class RewardResponseDto { type: String, example: 'url.png', }) - thumbnail: string; + thumbnail?: string; @ApiProperty({ description: 'type of reward', diff --git a/src/reward/reward.controllers.ts b/src/reward/reward.controllers.ts index 86ffcf4..5ee8162 100644 --- a/src/reward/reward.controllers.ts +++ b/src/reward/reward.controllers.ts @@ -40,6 +40,8 @@ import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto' import { Folder } from 'src/file/enums/folder.enum'; import { FileInterceptor } from '@nestjs/platform-express'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { PaginateRewardQueryDto } from './dtos/paginate-reward-query.dto'; +import { Type } from './enums/type.enum'; @Controller('reward') @Injectable() @@ -76,9 +78,15 @@ export class RewardController { required: false, description: 'Search by name', }) + @ApiQuery({ + name: 'type', + type: String, + required: false, + description: `search by ${Type.BADGE} ${Type.CERTIFICATE} or ${Type.ITEM}`, + }) async findAll( @Req() request: AuthenticatedRequest, - @Query() query: PaginateQueryDto, + @Query() query: PaginateRewardQueryDto, ): Promise { return this.rewardService.findAll(query, request.user.role); } @@ -134,9 +142,9 @@ export class RewardController { ) id: string, @Req() request: AuthenticatedRequest, - ): Promise { + ): Promise { const reward = await this.rewardService.findOne(id, request.user.role); - const file = await this.fileService.get(Folder.PROFILES, id); + const file = await this.fileService.get(Folder.REWARD_THUMBNAILS, id); return new StreamableFile(file, { disposition: 'inline', type: `image/${reward.thumbnail.split('.').pop()}`, @@ -145,10 +153,10 @@ export class RewardController { @Patch('thumbnail/:id') @Roles(Role.ADMIN) - @UseInterceptors(FileInterceptor('file')) @HttpCode(HttpStatus.NO_CONTENT) + @UseInterceptors(FileInterceptor('file')) @ApiResponse({ - description: 'upload file thumbnail of reward', + description: 'upload file thumbnail of reward successfully', status: HttpStatus.NO_CONTENT, }) @ApiConsumes('multipart/form-data') @@ -168,7 +176,7 @@ export class RewardController { new ParseFilePipeBuilder() .addFileTypeValidator({ fileType: 'image/*' }) .build({ - fileIsRequired: false, + fileIsRequired: true, errorHttpStatusCode: HttpStatus.BAD_REQUEST, }), ) diff --git a/src/reward/reward.entity.ts b/src/reward/reward.entity.ts index 5878383..feb8727 100644 --- a/src/reward/reward.entity.ts +++ b/src/reward/reward.entity.ts @@ -22,10 +22,15 @@ export class Reward { }) name: string; - @Column({}) + @Column({ + nullable: true, + }) description: string; - @Column() + @Column({ + nullable: true, + default: null, + }) thumbnail: string; @Column({ diff --git a/src/reward/reward.service.ts b/src/reward/reward.service.ts index 8a5a0cd..de04151 100644 --- a/src/reward/reward.service.ts +++ b/src/reward/reward.service.ts @@ -13,6 +13,7 @@ import { createPagination } from 'src/shared/pagination'; import { PaginatedRewardResponseDto } from './dtos/reward-response.dto'; import { Status } from './enums/status.enum'; import { Role } from 'src/shared/enums'; +import { Type } from './enums/type.enum'; @Injectable() export class RewardService { @@ -27,10 +28,12 @@ export class RewardService { page = 1, limit = 20, search = '', + type = '', }: { page?: number; limit?: number; search?: string; + type?: Type | ''; }, role?: Role, ): Promise { @@ -38,14 +41,23 @@ export class RewardService { page, limit, }); + const conditionSearch = search ? { name: ILike(`%${search}%`) } : {}; + const conditionType = type ? { type: type } : {}; if (role && role === Role.ADMIN) { const rewards = await find({ - where: { name: ILike(`%${search}%`) }, + where: { + ...conditionSearch, + ...conditionType, + }, }).run(); return rewards; } const rewards = await find({ - where: { name: ILike(`%${search}%`), status: Status.ACTIVE }, + where: { + ...conditionSearch, + ...conditionType, + status: Status.ACTIVE, + }, }).run(); return rewards; } From b47729bdee3888171233b3de3aaeda0d5a06f28f Mon Sep 17 00:00:00 2001 From: Potsawee Date: Thu, 21 Nov 2024 12:35:04 +0700 Subject: [PATCH 100/155] upload reward thumbnail --- src/category/category.controller.ts | 2 +- .../dtos/paginate-query-slug.dto.ts | 0 src/reward/reward.controllers.ts | 17 ++++++++++++----- src/reward/reward.service.ts | 6 ++++++ 4 files changed, 19 insertions(+), 6 deletions(-) rename src/{exam => category}/dtos/paginate-query-slug.dto.ts (100%) diff --git a/src/category/category.controller.ts b/src/category/category.controller.ts index 47d1815..78ccf24 100644 --- a/src/category/category.controller.ts +++ b/src/category/category.controller.ts @@ -26,7 +26,7 @@ import { Roles } from 'src/shared/decorators/role.decorator'; import { Role } from 'src/shared/enums'; import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { Slug } from './enums/slug.enum'; -import { PaginateCategoryQueryDto } from 'src/exam/dtos/paginate-query-slug.dto'; +import { PaginateCategoryQueryDto } from 'src/category/dtos/paginate-query-slug.dto'; @Controller('category') @Injectable() diff --git a/src/exam/dtos/paginate-query-slug.dto.ts b/src/category/dtos/paginate-query-slug.dto.ts similarity index 100% rename from src/exam/dtos/paginate-query-slug.dto.ts rename to src/category/dtos/paginate-query-slug.dto.ts diff --git a/src/reward/reward.controllers.ts b/src/reward/reward.controllers.ts index 5ee8162..9282c24 100644 --- a/src/reward/reward.controllers.ts +++ b/src/reward/reward.controllers.ts @@ -144,7 +144,10 @@ export class RewardController { @Req() request: AuthenticatedRequest, ): Promise { const reward = await this.rewardService.findOne(id, request.user.role); - const file = await this.fileService.get(Folder.REWARD_THUMBNAILS, id); + const file = await this.fileService.get( + Folder.REWARD_THUMBNAILS, + reward.thumbnail, + ); return new StreamableFile(file, { disposition: 'inline', type: `image/${reward.thumbnail.split('.').pop()}`, @@ -193,13 +196,17 @@ export class RewardController { ): Promise { const reward = await this.rewardService.findOne(id, request.user.role); if (reward.thumbnail) - await this.fileService.update(Folder.REWARD_THUMBNAILS, id, file); + await this.fileService.update( + Folder.REWARD_THUMBNAILS, + reward.thumbnail, + file, + ); else { await this.fileService.upload(Folder.REWARD_THUMBNAILS, id, file); - await this.rewardService.update(id, { - thumbnail: `${id}.${file.mimetype.split('/').pop()}`, - }); } + await this.rewardService.update(id, { + thumbnail: `${id}.${file.originalname.split('.').pop()}`, + }); } @Patch(':id') diff --git a/src/reward/reward.service.ts b/src/reward/reward.service.ts index de04151..0bfbb7d 100644 --- a/src/reward/reward.service.ts +++ b/src/reward/reward.service.ts @@ -72,6 +72,12 @@ export class RewardService { async create(CreateRewardDto: CreateRewardDto): Promise { try { + const reward = await this.rewardRepository.findOne({ + where: { + name: CreateRewardDto.name, + }, + }); + if (reward) throw new BadRequestException('reward already exists'); if (CreateRewardDto.points < 0) throw new BadRequestException('points should not be less than zero'); return this.rewardRepository.save(CreateRewardDto); From bf991153a4ca5cb96e82f20d23ecbcbdbfc39dec Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Thu, 21 Nov 2024 16:24:22 +0700 Subject: [PATCH 101/155] feat: create question and choice from data --- src/exam/exam.controller.ts | 20 +++++++++ src/exam/exam.module.ts | 10 ++++- src/exam/exam.service.ts | 81 ++++++++++++++++++++++++++++++++++++- 3 files changed, 108 insertions(+), 3 deletions(-) diff --git a/src/exam/exam.controller.ts b/src/exam/exam.controller.ts index f024de6..c4ba519 100644 --- a/src/exam/exam.controller.ts +++ b/src/exam/exam.controller.ts @@ -32,6 +32,7 @@ import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto' import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { UpdateExamDto } from './dtos/update-exam.dto'; import { PaginatedQuestionResponseDto } from 'src/question/dtos/question-response.dto'; +import { Public } from 'src/shared/decorators/public.decorator'; @Controller('exam') @ApiTags('Exam') @@ -165,4 +166,23 @@ export class ExamController { ): Promise { await this.examService.deleteExam(request.user.id, request.user.role, id); } + + @Post('generate/:examId') + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Create an question and question option in exam', + }) + @HttpCode(HttpStatus.CREATED) + async createQuestionAndChoice( + @Param( + 'examId', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + examId: string, + ): Promise { + return this.examService.createQuestionAndChoice(examId); + } } diff --git a/src/exam/exam.module.ts b/src/exam/exam.module.ts index 39b0384..5e4e1ae 100644 --- a/src/exam/exam.module.ts +++ b/src/exam/exam.module.ts @@ -6,9 +6,17 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { Exam } from './exam.entity'; import { examProviders } from './exam.providers'; import { CourseModule } from 'src/course-module/course-module.entity'; +import { QuestionModule } from 'src/question/question.module'; +import { QuestionOptionModule } from 'src/question-option/question-option.module'; +import { ExamAnswerModule } from 'src/exam-answer/exam-answer.module'; @Module({ - imports: [DatabaseModule, TypeOrmModule.forFeature([Exam, CourseModule])], + imports: [ + DatabaseModule, + TypeOrmModule.forFeature([Exam, CourseModule]), + QuestionModule, + QuestionOptionModule, + ], controllers: [ExamController], providers: [...examProviders, ExamService], exports: [ExamService], diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts index 55e8b79..0a7d059 100644 --- a/src/exam/exam.service.ts +++ b/src/exam/exam.service.ts @@ -16,10 +16,11 @@ import { Exam } from './exam.entity'; import { CreateExamDto } from './dtos/create-exam.dto'; import { PaginatedExamResponseDto } from './dtos/exam-response.dto'; import { createPagination } from 'src/shared/pagination'; -import { ExamStatus, Role } from 'src/shared/enums'; -import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { ExamStatus, QuestionType, Role } from 'src/shared/enums'; import { UpdateExamDto } from './dtos/update-exam.dto'; import { CourseModule } from 'src/course-module/course-module.entity'; +import { QuestionService } from 'src/question/question.service'; +import { QuestionOptionService } from 'src/question-option/question-option.service'; @Injectable() export class ExamService { @@ -28,6 +29,8 @@ export class ExamService { private readonly examRepository: Repository, @Inject('CourseModuleRepository') private readonly courseModuleRepository: Repository, + private readonly questionService: QuestionService, + private readonly questionOptionService: QuestionOptionService, ) {} async findAll( @@ -230,4 +233,78 @@ export class ExamService { return false; } } + + async fetchData() { + return { + message: 'Generate comment successful', + data: [ + { + question: "What is the purpose of the 'print()' function in Python?", + choices: { + a: 'To import libraries', + b: 'To create a new variable', + c: 'To display output on the screen', + d: 'To end a loop', + }, + answer: 'c', + }, + { + question: 'What data type is represented by the number 5 in Python?', + choices: { + a: 'String', + b: 'Integer', + c: 'Float', + d: 'Boolean', + }, + answer: 'b', + }, + { + question: + 'What is the difference between a list and a tuple in Python?', + choices: { + a: 'A list is mutable and a tuple is immutable.', + b: 'A list is immutable and a tuple is mutable.', + c: 'Both lists and tuples are mutable.', + d: 'Both lists and tuples are immutable.', + }, + answer: 'a', + }, + ], + }; + } + + async createQuestionAndChoice(examId: string): Promise { + const fetchData = await this.fetchData(); + let orderIndex = (await this.questionService.getMaxOrderIndex(examId)) + 1; + await Promise.all( + fetchData.data.map(async (data) => { + const createQuestionDto = { + examId, + question: data.question, + type: QuestionType.MULTIPLE_CHOICE, + points: 1, + orderIndex: orderIndex++, + }; + + const question = await this.questionService.createQuestion( + createQuestionDto, + ); + + await Promise.all( + Object.entries(data.choices).map(([key, value]) => { + const createQuestionOptionDto = { + questionId: question.id, + optionText: `${key}. ${value}`, + isCorrect: key === data.answer, + explanation: '', + }; + + return this.questionOptionService.createQuestionOption( + createQuestionOptionDto, + ); + }), + ); + }), + ); + } } From c389ce135b223e3aa36a29fb63dd3c8d2c37d03b Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Thu, 21 Nov 2024 16:41:44 +0700 Subject: [PATCH 102/155] feat: student can crud exam-answer --- src/exam-answer/exam-answer.controller.ts | 19 +++++-------------- src/exam-answer/exam-answer.service.ts | 23 +++++++++++++---------- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/exam-answer/exam-answer.controller.ts b/src/exam-answer/exam-answer.controller.ts index 3fc4264..fcae667 100644 --- a/src/exam-answer/exam-answer.controller.ts +++ b/src/exam-answer/exam-answer.controller.ts @@ -34,8 +34,6 @@ export class ExamAnswerController { constructor(private readonly examAnswerService: ExamAnswerService) {} @Get() - @Roles(Role.TEACHER) - @Roles(Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, description: 'Returns all exam answers', @@ -76,8 +74,6 @@ export class ExamAnswerController { } @Get(':id') - @Roles(Role.TEACHER) - @Roles(Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, description: 'Returns an exam answer', @@ -105,8 +101,6 @@ export class ExamAnswerController { } @Get('question/:questionId') - @Roles(Role.TEACHER) - @Roles(Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, description: 'Returns all exam answer in question', @@ -156,8 +150,6 @@ export class ExamAnswerController { } @Get('selected-option/:selectedOptionId') - @Roles(Role.TEACHER) - @Roles(Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, description: 'Returns all exam answer in selectedOption', @@ -207,7 +199,7 @@ export class ExamAnswerController { } @Post() - @Roles(Role.TEACHER) + @Roles(Role.STUDENT) @ApiResponse({ status: HttpStatus.CREATED, description: 'Create an exam answer', @@ -217,13 +209,14 @@ export class ExamAnswerController { async createExamAnswer( @Body() createExamAnswerDto: CreateExamAnswerDto, ): Promise { - const examAnswer = - await this.examAnswerService.createExamAnswer(createExamAnswerDto); + const examAnswer = await this.examAnswerService.createExamAnswer( + createExamAnswerDto, + ); return new ExamAnswerResponseDto(examAnswer); } @Patch(':id') - @Roles(Role.TEACHER) + @Roles(Role.STUDENT) @ApiResponse({ status: HttpStatus.OK, description: 'Update an exam answer', @@ -251,8 +244,6 @@ export class ExamAnswerController { } @Delete(':id') - @Roles(Role.TEACHER) - @Roles(Role.ADMIN) @ApiResponse({ status: HttpStatus.NO_CONTENT, description: 'Delete an exam', diff --git a/src/exam-answer/exam-answer.service.ts b/src/exam-answer/exam-answer.service.ts index b115f61..a530da8 100644 --- a/src/exam-answer/exam-answer.service.ts +++ b/src/exam-answer/exam-answer.service.ts @@ -80,6 +80,17 @@ export class ExamAnswerService { ): FindOptionsWhere | FindOptionsWhere[] { const baseSearch = search ? { answerText: ILike(`%${search}%`) } : {}; + if (role === Role.STUDENT) { + return [ + { + ...baseSearch, + examAttempt: { + userId, + }, + }, + ]; + } + if (role === Role.TEACHER) { return [ { @@ -106,16 +117,8 @@ export class ExamAnswerService { return [ { ...baseSearch, - question: { - exam: { - courseModule: { - course: { - teacher: { - id: userId, - }, - }, - }, - }, + examAttempt: { + userId, }, }, ]; From 614cf872607f12f6bd1f37bb593f8912369a783c Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Mon, 25 Nov 2024 11:02:36 +0700 Subject: [PATCH 103/155] Add transcribeAudio feature to ChapterController and ChapterService, include HttpModule in ChapterModule --- package.json | 2 + pnpm-lock.yaml | 266 ++++++++++---------- src/chapter/chapter.controller.ts | 16 ++ src/chapter/chapter.module.ts | 4 +- src/chapter/chapter.service.ts | 5 + src/chapter/dtos/transcribe-response.dto.ts | 0 src/course/course.controller.ts | 7 +- 7 files changed, 171 insertions(+), 129 deletions(-) create mode 100644 src/chapter/dtos/transcribe-response.dto.ts diff --git a/package.json b/package.json index ecd4a51..8183d6c 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ }, "dependencies": { "@aws-sdk/client-s3": "^3.693.0", + "@nestjs/axios": "^3.1.2", "@nestjs/class-validator": "0.13.1", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.3.0", @@ -37,6 +38,7 @@ "@nestjs/typeorm": "^10.0.2", "@smithy/types": "^3.7.1", "argon2": "^0.41.1", + "axios": "^1.7.7", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dotenv": "^16.4.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3d564b..560d1e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@aws-sdk/client-s3': specifier: ^3.693.0 version: 3.693.0 + '@nestjs/axios': + specifier: ^3.1.2 + version: 3.1.2(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(axios@1.7.7)(rxjs@7.8.1) '@nestjs/class-validator': specifier: 0.13.1 version: 0.13.1 @@ -19,31 +22,34 @@ importers: version: 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) '@nestjs/config': specifier: ^3.3.0 - version: 3.3.0(@nestjs/common@10.0.0)(rxjs@7.8.1) + version: 3.3.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(rxjs@7.8.1) '@nestjs/core': specifier: ^10.0.0 - version: 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + version: 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) '@nestjs/jwt': specifier: ^10.2.0 - version: 10.2.0(@nestjs/common@10.0.0) + version: 10.2.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1)) '@nestjs/mapped-types': specifier: ^2.0.6 - version: 2.0.6(@nestjs/common@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) + version: 2.0.6(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) '@nestjs/platform-express': specifier: ^10.0.0 - version: 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0) + version: 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) '@nestjs/swagger': specifier: ^8.0.5 - version: 8.0.5(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) + version: 8.0.5(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) '@nestjs/typeorm': specifier: ^10.0.2 - version: 10.0.2(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)(typeorm@0.3.20) + version: 10.0.2(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3))) '@smithy/types': specifier: ^3.7.1 version: 3.7.1 argon2: specifier: ^0.41.1 version: 0.41.1 + axios: + specifier: ^1.7.7 + version: 1.7.7 class-transformer: specifier: ^0.5.1 version: 0.5.1 @@ -70,10 +76,10 @@ importers: version: 7.8.1 typeorm: specifier: ^0.3.20 - version: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1) + version: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) typeorm-extension: specifier: ^3.6.3 - version: 3.6.3(typeorm@0.3.20) + version: 3.6.3(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3))) devDependencies: '@nestjs/cli': specifier: ^10.0.0 @@ -83,7 +89,7 @@ importers: version: 10.0.0(chokidar@3.5.3)(typescript@5.1.3) '@nestjs/testing': specifier: ^10.0.0 - version: 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0) + version: 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0) '@types/express': specifier: ^5.0.0 version: 5.0.0 @@ -101,7 +107,7 @@ importers: version: 6.0.0 '@typescript-eslint/eslint-plugin': specifier: ^8.0.0 - version: 8.0.0(@typescript-eslint/parser@8.0.0)(eslint@8.0.0)(typescript@5.1.3) + version: 8.0.0(@typescript-eslint/parser@8.0.0(eslint@8.0.0)(typescript@5.1.3))(eslint@8.0.0)(typescript@5.1.3) '@typescript-eslint/parser': specifier: ^8.0.0 version: 8.0.0(eslint@8.0.0)(typescript@5.1.3) @@ -113,10 +119,10 @@ importers: version: 9.0.0(eslint@8.0.0) eslint-plugin-prettier: specifier: ^5.0.0 - version: 5.0.0(eslint-config-prettier@9.0.0)(eslint@8.0.0)(prettier@3.0.0) + version: 5.0.0(@types/eslint@9.6.1)(eslint-config-prettier@9.0.0(eslint@8.0.0))(eslint@8.0.0)(prettier@3.0.0) jest: specifier: ^29.5.0 - version: 29.5.0(@types/node@20.3.1)(ts-node@10.9.1) + version: 29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) prettier: specifier: ^3.0.0 version: 3.0.0 @@ -128,10 +134,10 @@ importers: version: 7.0.0 ts-jest: specifier: ^29.1.0 - version: 29.1.0(@babel/core@7.26.0)(jest@29.5.0)(typescript@5.1.3) + version: 29.1.0(@babel/core@7.26.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)))(typescript@5.1.3) ts-loader: specifier: ^9.4.3 - version: 9.4.3(typescript@5.1.3)(webpack@5.96.1) + version: 9.4.3(typescript@5.1.3)(webpack@5.87.0) ts-node: specifier: ^10.9.1 version: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) @@ -640,6 +646,13 @@ packages: '@microsoft/tsdoc@0.15.0': resolution: {integrity: sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==} + '@nestjs/axios@3.1.2': + resolution: {integrity: sha512-pFlfi4ZQsZtTNNhvgssbxjCHUd1nMpV3sXy/xOOB2uEJhw3M8j8SFR08gjFNil2we2Har7VCsXLfCkwbMHECFQ==} + peerDependencies: + '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + axios: ^1.3.1 + rxjs: ^6.0.0 || ^7.0.0 + '@nestjs/class-validator@0.13.1': resolution: {integrity: sha512-Wwpg9KuGnkWOFleBPEFdHQKwqZsF/PFWQaeSaXwatkofgqD7N6AV5J30xIVgScDP+IcE+MdPGZJ38vHQ24KxPQ==} @@ -1359,6 +1372,9 @@ packages: resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} engines: {node: '>= 6.0.0'} + axios@1.7.7: + resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} + babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1963,6 +1979,15 @@ packages: flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + foreground-child@3.3.0: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} @@ -2924,6 +2949,9 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} @@ -3549,16 +3577,6 @@ packages: webpack-cli: optional: true - webpack@5.96.1: - resolution: {integrity: sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==} - engines: {node: '>=10.13.0'} - hasBin: true - peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true - whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -3645,10 +3663,11 @@ snapshots: dependencies: ajv: 8.12.0 ajv-formats: 2.1.1(ajv@8.12.0) - chokidar: 3.5.3 jsonc-parser: 3.2.0 rxjs: 7.8.1 source-map: 0.7.4 + optionalDependencies: + chokidar: 3.5.3 '@angular-devkit/schematics-cli@16.1.0(chokidar@3.5.3)': dependencies: @@ -3726,7 +3745,7 @@ snapshots: '@aws-sdk/client-sso-oidc': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/client-sts': 3.693.0 '@aws-sdk/core': 3.693.0 - '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) '@aws-sdk/middleware-bucket-endpoint': 3.693.0 '@aws-sdk/middleware-expect-continue': 3.693.0 '@aws-sdk/middleware-flexible-checksums': 3.693.0 @@ -3787,7 +3806,7 @@ snapshots: '@aws-crypto/sha256-js': 5.2.0 '@aws-sdk/client-sts': 3.693.0 '@aws-sdk/core': 3.693.0 - '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) '@aws-sdk/middleware-host-header': 3.693.0 '@aws-sdk/middleware-logger': 3.693.0 '@aws-sdk/middleware-recursion-detection': 3.693.0 @@ -3875,7 +3894,7 @@ snapshots: '@aws-crypto/sha256-js': 5.2.0 '@aws-sdk/client-sso-oidc': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/core': 3.693.0 - '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) '@aws-sdk/middleware-host-header': 3.693.0 '@aws-sdk/middleware-logger': 3.693.0 '@aws-sdk/middleware-recursion-detection': 3.693.0 @@ -3949,14 +3968,14 @@ snapshots: '@smithy/util-stream': 3.3.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0)': + '@aws-sdk/credential-provider-ini@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0)': dependencies: '@aws-sdk/client-sts': 3.693.0 '@aws-sdk/core': 3.693.0 '@aws-sdk/credential-provider-env': 3.693.0 '@aws-sdk/credential-provider-http': 3.693.0 '@aws-sdk/credential-provider-process': 3.693.0 - '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0) + '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)) '@aws-sdk/credential-provider-web-identity': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/types': 3.692.0 '@smithy/credential-provider-imds': 3.2.7 @@ -3968,13 +3987,13 @@ snapshots: - '@aws-sdk/client-sso-oidc' - aws-crt - '@aws-sdk/credential-provider-node@3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0)': + '@aws-sdk/credential-provider-node@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0)': dependencies: '@aws-sdk/credential-provider-env': 3.693.0 '@aws-sdk/credential-provider-http': 3.693.0 - '@aws-sdk/credential-provider-ini': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-ini': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) '@aws-sdk/credential-provider-process': 3.693.0 - '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0) + '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)) '@aws-sdk/credential-provider-web-identity': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/types': 3.692.0 '@smithy/credential-provider-imds': 3.2.7 @@ -3996,11 +4015,11 @@ snapshots: '@smithy/types': 3.7.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.693.0(@aws-sdk/client-sso-oidc@3.693.0)': + '@aws-sdk/credential-provider-sso@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))': dependencies: '@aws-sdk/client-sso': 3.693.0 '@aws-sdk/core': 3.693.0 - '@aws-sdk/token-providers': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0) + '@aws-sdk/token-providers': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)) '@aws-sdk/types': 3.692.0 '@smithy/property-provider': 3.1.10 '@smithy/shared-ini-file-loader': 3.1.11 @@ -4129,7 +4148,7 @@ snapshots: '@smithy/types': 3.7.1 tslib: 2.8.1 - '@aws-sdk/token-providers@3.693.0(@aws-sdk/client-sso-oidc@3.693.0)': + '@aws-sdk/token-providers@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))': dependencies: '@aws-sdk/client-sso-oidc': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/types': 3.692.0 @@ -4439,7 +4458,7 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(ts-node@10.9.1)': + '@jest/core@29.7.0(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -4453,7 +4472,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) + jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -4623,6 +4642,12 @@ snapshots: '@microsoft/tsdoc@0.15.0': {} + '@nestjs/axios@3.1.2(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(axios@1.7.7)(rxjs@7.8.1)': + dependencies: + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + axios: 1.7.7 + rxjs: 7.8.1 + '@nestjs/class-validator@0.13.1': dependencies: '@types/validator': 13.12.2 @@ -4660,15 +4685,16 @@ snapshots: '@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1)': dependencies: - class-transformer: 0.5.1 - class-validator: 0.14.1 iterare: 1.2.1 reflect-metadata: 0.2.0 rxjs: 7.8.1 tslib: 2.5.3 uid: 2.0.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.1 - '@nestjs/config@3.3.0(@nestjs/common@10.0.0)(rxjs@7.8.1)': + '@nestjs/config@3.3.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(rxjs@7.8.1)': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) dotenv: 16.4.5 @@ -4676,10 +4702,9 @@ snapshots: lodash: 4.17.21 rxjs: 7.8.1 - '@nestjs/core@10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)': + '@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0) '@nuxtjs/opencollective': 0.3.2 fast-safe-stringify: 2.1.1 iterare: 1.2.1 @@ -4688,26 +4713,29 @@ snapshots: rxjs: 7.8.1 tslib: 2.5.3 uid: 2.0.2 + optionalDependencies: + '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) transitivePeerDependencies: - encoding - '@nestjs/jwt@10.2.0(@nestjs/common@10.0.0)': + '@nestjs/jwt@10.2.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) '@types/jsonwebtoken': 9.0.5 jsonwebtoken: 9.0.2 - '@nestjs/mapped-types@2.0.6(@nestjs/common@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)': + '@nestjs/mapped-types@2.0.6(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + reflect-metadata: 0.2.0 + optionalDependencies: class-transformer: 0.5.1 class-validator: 0.14.1 - reflect-metadata: 0.2.0 - '@nestjs/platform-express@10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)': + '@nestjs/platform-express@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) body-parser: 1.20.2 cors: 2.8.5 express: 4.18.2 @@ -4727,34 +4755,36 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/swagger@8.0.5(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)': + '@nestjs/swagger@8.0.5(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)': dependencies: '@microsoft/tsdoc': 0.15.0 '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/mapped-types': 2.0.6(@nestjs/common@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) - class-transformer: 0.5.1 - class-validator: 0.14.1 + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/mapped-types': 2.0.6(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) js-yaml: 4.1.0 lodash: 4.17.21 path-to-regexp: 3.3.0 reflect-metadata: 0.2.0 swagger-ui-dist: 5.18.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.1 - '@nestjs/testing@10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0)': + '@nestjs/testing@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0)': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) tslib: 2.5.3 + optionalDependencies: + '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) - '@nestjs/typeorm@10.0.2(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)(typeorm@0.3.20)': + '@nestjs/typeorm@10.0.2(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)))': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) reflect-metadata: 0.2.0 rxjs: 7.8.1 - typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1) + typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) uuid: 9.0.1 '@nodelib/fs.scandir@2.1.5': @@ -5280,7 +5310,7 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0)(eslint@8.0.0)(typescript@5.1.3)': + '@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0(eslint@8.0.0)(typescript@5.1.3))(eslint@8.0.0)(typescript@5.1.3)': dependencies: '@eslint-community/regexpp': 4.12.1 '@typescript-eslint/parser': 8.0.0(eslint@8.0.0)(typescript@5.1.3) @@ -5293,6 +5323,7 @@ snapshots: ignore: 5.3.2 natural-compare: 1.4.0 ts-api-utils: 1.4.0(typescript@5.1.3) + optionalDependencies: typescript: 5.1.3 transitivePeerDependencies: - supports-color @@ -5305,6 +5336,7 @@ snapshots: '@typescript-eslint/visitor-keys': 8.0.0 debug: 4.3.7 eslint: 8.0.0 + optionalDependencies: typescript: 5.1.3 transitivePeerDependencies: - supports-color @@ -5320,6 +5352,7 @@ snapshots: '@typescript-eslint/utils': 8.0.0(eslint@8.0.0)(typescript@5.1.3) debug: 4.3.7 ts-api-utils: 1.4.0(typescript@5.1.3) + optionalDependencies: typescript: 5.1.3 transitivePeerDependencies: - eslint @@ -5337,6 +5370,7 @@ snapshots: minimatch: 9.0.5 semver: 7.6.3 ts-api-utils: 1.4.0(typescript@5.1.3) + optionalDependencies: typescript: 5.1.3 transitivePeerDependencies: - supports-color @@ -5457,7 +5491,7 @@ snapshots: acorn@8.14.0: {} ajv-formats@2.1.1(ajv@8.12.0): - dependencies: + optionalDependencies: ajv: 8.12.0 ajv-keywords@3.5.2(ajv@6.12.6): @@ -5533,6 +5567,14 @@ snapshots: aws-ssl-profiles@1.1.2: {} + axios@1.7.7: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.1 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + babel-jest@29.7.0(@babel/core@7.26.0): dependencies: '@babel/core': 7.26.0 @@ -5842,13 +5884,13 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 - create-jest@29.7.0(@types/node@20.3.1)(ts-node@10.9.1): + create-jest@29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) + jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -5992,13 +6034,15 @@ snapshots: dependencies: eslint: 8.0.0 - eslint-plugin-prettier@5.0.0(eslint-config-prettier@9.0.0)(eslint@8.0.0)(prettier@3.0.0): + eslint-plugin-prettier@5.0.0(@types/eslint@9.6.1)(eslint-config-prettier@9.0.0(eslint@8.0.0))(eslint@8.0.0)(prettier@3.0.0): dependencies: eslint: 8.0.0 - eslint-config-prettier: 9.0.0(eslint@8.0.0) prettier: 3.0.0 prettier-linter-helpers: 1.0.0 synckit: 0.8.8 + optionalDependencies: + '@types/eslint': 9.6.1 + eslint-config-prettier: 9.0.0(eslint@8.0.0) eslint-scope@5.1.1: dependencies: @@ -6233,6 +6277,8 @@ snapshots: flatted@3.3.1: {} + follow-redirects@1.15.9: {} + foreground-child@3.3.0: dependencies: cross-spawn: 7.0.5 @@ -6592,16 +6638,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.3.1)(ts-node@10.9.1): + jest-cli@29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.1) + '@jest/core': 29.7.0(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) + create-jest: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) + jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -6611,12 +6657,11 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.3.1)(ts-node@10.9.1): + jest-config@29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): dependencies: '@babel/core': 7.26.0 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.3.1 babel-jest: 29.7.0(@babel/core@7.26.0) chalk: 4.1.2 ci-info: 3.9.0 @@ -6636,6 +6681,8 @@ snapshots: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.3.1 ts-node: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) transitivePeerDependencies: - babel-plugin-macros @@ -6718,7 +6765,7 @@ snapshots: jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): - dependencies: + optionalDependencies: jest-resolve: 29.7.0 jest-regex-util@29.6.3: {} @@ -6862,12 +6909,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.5.0(@types/node@20.3.1)(ts-node@10.9.1): + jest@29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.1) + '@jest/core': 29.7.0(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) + jest-cli: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -7350,6 +7397,8 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + proxy-from-env@1.1.0: {} + pump@3.0.2: dependencies: end-of-stream: 1.4.4 @@ -7693,15 +7742,6 @@ snapshots: terser: 5.36.0 webpack: 5.87.0 - terser-webpack-plugin@5.3.10(webpack@5.96.1): - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - jest-worker: 27.5.1 - schema-utils: 3.3.0 - serialize-javascript: 6.0.2 - terser: 5.36.0 - webpack: 5.96.1 - terser@5.36.0: dependencies: '@jridgewell/source-map': 0.3.6 @@ -7747,12 +7787,11 @@ snapshots: dependencies: typescript: 5.1.3 - ts-jest@29.1.0(@babel/core@7.26.0)(jest@29.5.0)(typescript@5.1.3): + ts-jest@29.1.0(@babel/core@7.26.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)))(typescript@5.1.3): dependencies: - '@babel/core': 7.26.0 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.5.0(@types/node@20.3.1)(ts-node@10.9.1) + jest: 29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -7760,15 +7799,19 @@ snapshots: semver: 7.6.3 typescript: 5.1.3 yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.26.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.0) - ts-loader@9.4.3(typescript@5.1.3)(webpack@5.96.1): + ts-loader@9.4.3(typescript@5.1.3)(webpack@5.87.0): dependencies: chalk: 4.1.2 enhanced-resolve: 5.17.1 micromatch: 4.0.8 semver: 7.6.3 typescript: 5.1.3 - webpack: 5.96.1 + webpack: 5.87.0 ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3): dependencies: @@ -7821,7 +7864,7 @@ snapshots: typedarray@0.0.6: {} - typeorm-extension@3.6.3(typeorm@0.3.20): + typeorm-extension@3.6.3(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3))): dependencies: '@faker-js/faker': 8.4.1 consola: 3.2.3 @@ -7831,10 +7874,10 @@ snapshots: rapiq: 0.9.0 reflect-metadata: 0.2.2 smob: 1.5.0 - typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1) + typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) yargs: 17.7.2 - typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1): + typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): dependencies: '@sqltools/formatter': 1.2.5 app-root-path: 3.1.0 @@ -7846,14 +7889,15 @@ snapshots: dotenv: 16.4.5 glob: 10.4.5 mkdirp: 2.1.6 - mysql2: 3.11.4 - pg: 8.13.1 reflect-metadata: 0.2.2 sha.js: 2.4.11 - ts-node: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) tslib: 2.8.1 uuid: 9.0.1 yargs: 17.7.2 + optionalDependencies: + mysql2: 3.11.4 + pg: 8.13.1 + ts-node: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) transitivePeerDependencies: - supports-color @@ -7947,36 +7991,6 @@ snapshots: - esbuild - uglify-js - webpack@5.96.1: - dependencies: - '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.6 - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/wasm-edit': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.14.0 - browserslist: 4.24.2 - chrome-trace-event: 1.0.4 - enhanced-resolve: 5.17.1 - es-module-lexer: 1.5.4 - eslint-scope: 5.1.1 - events: 3.3.0 - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.0 - mime-types: 2.1.35 - neo-async: 2.6.2 - schema-utils: 3.3.0 - tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(webpack@5.96.1) - watchpack: 2.4.2 - webpack-sources: 3.2.3 - transitivePeerDependencies: - - '@swc/core' - - esbuild - - uglify-js - whatwg-url@5.0.0: dependencies: tr46: 0.0.3 diff --git a/src/chapter/chapter.controller.ts b/src/chapter/chapter.controller.ts index 0e61978..7ffabce 100644 --- a/src/chapter/chapter.controller.ts +++ b/src/chapter/chapter.controller.ts @@ -251,6 +251,22 @@ export class ChapterController { return this.chapterService.create(createChapterDto); } + @Post('transcribe/:id') + @ApiResponse({ + status: HttpStatus.OK, + type: ChapterResponseDto, + description: 'Update a chapter', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'Chapter ID', + }) + async transcribe(@Param('id', ParseUUIDPipe) id: string) { + return await this.chapterService.transcribeAudio(id); + } + + @Patch(':id') @CourseOwnership({ adminDraftOnly: true }) @ApiResponse({ diff --git a/src/chapter/chapter.module.ts b/src/chapter/chapter.module.ts index 8c81889..12e7d23 100644 --- a/src/chapter/chapter.module.ts +++ b/src/chapter/chapter.module.ts @@ -10,6 +10,7 @@ import { CourseModule } from 'src/course-module/course-module.entity'; import { ChatRoomModule } from 'src/chat-room/chat-room.module'; import { EnrollmentModule } from 'src/enrollment/enrollment.module'; import { FileModule } from 'src/file/file.module'; +import { HttpModule } from '@nestjs/axios'; @Module({ imports: [ @@ -18,7 +19,8 @@ import { FileModule } from 'src/file/file.module'; TypeOrmModule.forFeature([Chapter, CourseModule]), ChatRoomModule, EnrollmentModule, - FileModule + FileModule, + HttpModule ], controllers: [ChapterController], providers: [...chapterProviders, ChapterService], diff --git a/src/chapter/chapter.service.ts b/src/chapter/chapter.service.ts index 87e3786..8260319 100644 --- a/src/chapter/chapter.service.ts +++ b/src/chapter/chapter.service.ts @@ -20,6 +20,7 @@ import { EnrollmentStatus } from 'src/enrollment/enums/enrollment-status.enum'; @Injectable() export class ChapterService { + constructor( @InjectRepository(Chapter) private readonly chapterRepository: Repository, @@ -195,6 +196,10 @@ export class ChapterService { return result; } + async transcribeAudio(id: string) { + throw new Error('Method not implemented.'); + } + private async validateOrderIndex( moduleId: string, orderIndex: number, diff --git a/src/chapter/dtos/transcribe-response.dto.ts b/src/chapter/dtos/transcribe-response.dto.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/course/course.controller.ts b/src/course/course.controller.ts index ebe715a..49e3e6f 100644 --- a/src/course/course.controller.ts +++ b/src/course/course.controller.ts @@ -47,7 +47,6 @@ import { FileInterceptor } from '@nestjs/platform-express'; @Controller('course') @ApiTags('Course') -@ApiBearerAuth() @Injectable() export class CourseController { constructor( @@ -151,7 +150,7 @@ export class CourseController { @ApiResponse({ status: HttpStatus.OK, type: CourseResponseDto, - description: 'Get all course', + description: 'Get all course by', isArray: true, }) @ApiQuery({ @@ -172,6 +171,7 @@ export class CourseController { required: false, description: 'Search by email', }) + @ApiBearerAuth() async findAll( @Req() request: AuthenticatedRequest, @Query() query: PaginateQueryDto, @@ -217,6 +217,7 @@ export class CourseController { @Post() @Roles(Role.TEACHER) + @ApiBearerAuth() @ApiResponse({ status: HttpStatus.CREATED, type: CourseResponseDto, @@ -243,6 +244,7 @@ export class CourseController { @Patch(':id') @CourseOwnership({ adminDraftOnly: true }) + @ApiBearerAuth() @ApiParam({ name: 'id', type: String, @@ -288,6 +290,7 @@ export class CourseController { status: HttpStatus.OK, description: 'Delete course by id', }) + @ApiBearerAuth() async delete( @Param( 'id', From ea7ea2c2f3474ab54fc5319644f55c1e0008d0e5 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Mon, 25 Nov 2024 11:52:06 +0700 Subject: [PATCH 104/155] feat: add AI_URL to environment config, implement findMany method in chat message service, and enhance user streak creation with increment logic --- .env.example | 3 +- package.json | 2 + pnpm-lock.yaml | 266 +++++++++--------- src/chat-message/chat-message.controller.ts | 27 +- src/chat-message/chat-message.module.ts | 2 + src/chat-message/chat-message.service.ts | 15 + src/chat-message/dtos/qa.dto.ts | 21 ++ src/shared/configs/dotenv.config.ts | 1 + .../constants/global-config.constant.ts | 1 + src/user-streak/user-streak.module.ts | 3 +- src/user-streak/user-streak.service.ts | 12 +- src/user/user.service.ts | 12 + 12 files changed, 235 insertions(+), 130 deletions(-) create mode 100644 src/chat-message/dtos/qa.dto.ts diff --git a/.env.example b/.env.example index a9bf602..59b9a08 100644 --- a/.env.example +++ b/.env.example @@ -15,4 +15,5 @@ AWS_SECRET_ACCESS_KEY=yoursecretkey AWS_REGION=yourregion AWS_BUCKET_NAME=yourbucketname ADMIN_EMAIL=admin@gmail.com -ADMIN_PASSWORD=P@ssword! \ No newline at end of file +ADMIN_PASSWORD=P@ssword! +AI_URL=https://localhost:5000 \ No newline at end of file diff --git a/package.json b/package.json index ecd4a51..8183d6c 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ }, "dependencies": { "@aws-sdk/client-s3": "^3.693.0", + "@nestjs/axios": "^3.1.2", "@nestjs/class-validator": "0.13.1", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.3.0", @@ -37,6 +38,7 @@ "@nestjs/typeorm": "^10.0.2", "@smithy/types": "^3.7.1", "argon2": "^0.41.1", + "axios": "^1.7.7", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dotenv": "^16.4.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3d564b..560d1e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@aws-sdk/client-s3': specifier: ^3.693.0 version: 3.693.0 + '@nestjs/axios': + specifier: ^3.1.2 + version: 3.1.2(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(axios@1.7.7)(rxjs@7.8.1) '@nestjs/class-validator': specifier: 0.13.1 version: 0.13.1 @@ -19,31 +22,34 @@ importers: version: 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) '@nestjs/config': specifier: ^3.3.0 - version: 3.3.0(@nestjs/common@10.0.0)(rxjs@7.8.1) + version: 3.3.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(rxjs@7.8.1) '@nestjs/core': specifier: ^10.0.0 - version: 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + version: 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) '@nestjs/jwt': specifier: ^10.2.0 - version: 10.2.0(@nestjs/common@10.0.0) + version: 10.2.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1)) '@nestjs/mapped-types': specifier: ^2.0.6 - version: 2.0.6(@nestjs/common@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) + version: 2.0.6(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) '@nestjs/platform-express': specifier: ^10.0.0 - version: 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0) + version: 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) '@nestjs/swagger': specifier: ^8.0.5 - version: 8.0.5(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) + version: 8.0.5(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) '@nestjs/typeorm': specifier: ^10.0.2 - version: 10.0.2(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)(typeorm@0.3.20) + version: 10.0.2(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3))) '@smithy/types': specifier: ^3.7.1 version: 3.7.1 argon2: specifier: ^0.41.1 version: 0.41.1 + axios: + specifier: ^1.7.7 + version: 1.7.7 class-transformer: specifier: ^0.5.1 version: 0.5.1 @@ -70,10 +76,10 @@ importers: version: 7.8.1 typeorm: specifier: ^0.3.20 - version: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1) + version: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) typeorm-extension: specifier: ^3.6.3 - version: 3.6.3(typeorm@0.3.20) + version: 3.6.3(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3))) devDependencies: '@nestjs/cli': specifier: ^10.0.0 @@ -83,7 +89,7 @@ importers: version: 10.0.0(chokidar@3.5.3)(typescript@5.1.3) '@nestjs/testing': specifier: ^10.0.0 - version: 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0) + version: 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0) '@types/express': specifier: ^5.0.0 version: 5.0.0 @@ -101,7 +107,7 @@ importers: version: 6.0.0 '@typescript-eslint/eslint-plugin': specifier: ^8.0.0 - version: 8.0.0(@typescript-eslint/parser@8.0.0)(eslint@8.0.0)(typescript@5.1.3) + version: 8.0.0(@typescript-eslint/parser@8.0.0(eslint@8.0.0)(typescript@5.1.3))(eslint@8.0.0)(typescript@5.1.3) '@typescript-eslint/parser': specifier: ^8.0.0 version: 8.0.0(eslint@8.0.0)(typescript@5.1.3) @@ -113,10 +119,10 @@ importers: version: 9.0.0(eslint@8.0.0) eslint-plugin-prettier: specifier: ^5.0.0 - version: 5.0.0(eslint-config-prettier@9.0.0)(eslint@8.0.0)(prettier@3.0.0) + version: 5.0.0(@types/eslint@9.6.1)(eslint-config-prettier@9.0.0(eslint@8.0.0))(eslint@8.0.0)(prettier@3.0.0) jest: specifier: ^29.5.0 - version: 29.5.0(@types/node@20.3.1)(ts-node@10.9.1) + version: 29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) prettier: specifier: ^3.0.0 version: 3.0.0 @@ -128,10 +134,10 @@ importers: version: 7.0.0 ts-jest: specifier: ^29.1.0 - version: 29.1.0(@babel/core@7.26.0)(jest@29.5.0)(typescript@5.1.3) + version: 29.1.0(@babel/core@7.26.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)))(typescript@5.1.3) ts-loader: specifier: ^9.4.3 - version: 9.4.3(typescript@5.1.3)(webpack@5.96.1) + version: 9.4.3(typescript@5.1.3)(webpack@5.87.0) ts-node: specifier: ^10.9.1 version: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) @@ -640,6 +646,13 @@ packages: '@microsoft/tsdoc@0.15.0': resolution: {integrity: sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==} + '@nestjs/axios@3.1.2': + resolution: {integrity: sha512-pFlfi4ZQsZtTNNhvgssbxjCHUd1nMpV3sXy/xOOB2uEJhw3M8j8SFR08gjFNil2we2Har7VCsXLfCkwbMHECFQ==} + peerDependencies: + '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + axios: ^1.3.1 + rxjs: ^6.0.0 || ^7.0.0 + '@nestjs/class-validator@0.13.1': resolution: {integrity: sha512-Wwpg9KuGnkWOFleBPEFdHQKwqZsF/PFWQaeSaXwatkofgqD7N6AV5J30xIVgScDP+IcE+MdPGZJ38vHQ24KxPQ==} @@ -1359,6 +1372,9 @@ packages: resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} engines: {node: '>= 6.0.0'} + axios@1.7.7: + resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} + babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1963,6 +1979,15 @@ packages: flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + foreground-child@3.3.0: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} @@ -2924,6 +2949,9 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} @@ -3549,16 +3577,6 @@ packages: webpack-cli: optional: true - webpack@5.96.1: - resolution: {integrity: sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==} - engines: {node: '>=10.13.0'} - hasBin: true - peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true - whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -3645,10 +3663,11 @@ snapshots: dependencies: ajv: 8.12.0 ajv-formats: 2.1.1(ajv@8.12.0) - chokidar: 3.5.3 jsonc-parser: 3.2.0 rxjs: 7.8.1 source-map: 0.7.4 + optionalDependencies: + chokidar: 3.5.3 '@angular-devkit/schematics-cli@16.1.0(chokidar@3.5.3)': dependencies: @@ -3726,7 +3745,7 @@ snapshots: '@aws-sdk/client-sso-oidc': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/client-sts': 3.693.0 '@aws-sdk/core': 3.693.0 - '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) '@aws-sdk/middleware-bucket-endpoint': 3.693.0 '@aws-sdk/middleware-expect-continue': 3.693.0 '@aws-sdk/middleware-flexible-checksums': 3.693.0 @@ -3787,7 +3806,7 @@ snapshots: '@aws-crypto/sha256-js': 5.2.0 '@aws-sdk/client-sts': 3.693.0 '@aws-sdk/core': 3.693.0 - '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) '@aws-sdk/middleware-host-header': 3.693.0 '@aws-sdk/middleware-logger': 3.693.0 '@aws-sdk/middleware-recursion-detection': 3.693.0 @@ -3875,7 +3894,7 @@ snapshots: '@aws-crypto/sha256-js': 5.2.0 '@aws-sdk/client-sso-oidc': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/core': 3.693.0 - '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) '@aws-sdk/middleware-host-header': 3.693.0 '@aws-sdk/middleware-logger': 3.693.0 '@aws-sdk/middleware-recursion-detection': 3.693.0 @@ -3949,14 +3968,14 @@ snapshots: '@smithy/util-stream': 3.3.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0)': + '@aws-sdk/credential-provider-ini@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0)': dependencies: '@aws-sdk/client-sts': 3.693.0 '@aws-sdk/core': 3.693.0 '@aws-sdk/credential-provider-env': 3.693.0 '@aws-sdk/credential-provider-http': 3.693.0 '@aws-sdk/credential-provider-process': 3.693.0 - '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0) + '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)) '@aws-sdk/credential-provider-web-identity': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/types': 3.692.0 '@smithy/credential-provider-imds': 3.2.7 @@ -3968,13 +3987,13 @@ snapshots: - '@aws-sdk/client-sso-oidc' - aws-crt - '@aws-sdk/credential-provider-node@3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0)': + '@aws-sdk/credential-provider-node@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0)': dependencies: '@aws-sdk/credential-provider-env': 3.693.0 '@aws-sdk/credential-provider-http': 3.693.0 - '@aws-sdk/credential-provider-ini': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-ini': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) '@aws-sdk/credential-provider-process': 3.693.0 - '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0) + '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)) '@aws-sdk/credential-provider-web-identity': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/types': 3.692.0 '@smithy/credential-provider-imds': 3.2.7 @@ -3996,11 +4015,11 @@ snapshots: '@smithy/types': 3.7.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.693.0(@aws-sdk/client-sso-oidc@3.693.0)': + '@aws-sdk/credential-provider-sso@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))': dependencies: '@aws-sdk/client-sso': 3.693.0 '@aws-sdk/core': 3.693.0 - '@aws-sdk/token-providers': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0) + '@aws-sdk/token-providers': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)) '@aws-sdk/types': 3.692.0 '@smithy/property-provider': 3.1.10 '@smithy/shared-ini-file-loader': 3.1.11 @@ -4129,7 +4148,7 @@ snapshots: '@smithy/types': 3.7.1 tslib: 2.8.1 - '@aws-sdk/token-providers@3.693.0(@aws-sdk/client-sso-oidc@3.693.0)': + '@aws-sdk/token-providers@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))': dependencies: '@aws-sdk/client-sso-oidc': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/types': 3.692.0 @@ -4439,7 +4458,7 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(ts-node@10.9.1)': + '@jest/core@29.7.0(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -4453,7 +4472,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) + jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -4623,6 +4642,12 @@ snapshots: '@microsoft/tsdoc@0.15.0': {} + '@nestjs/axios@3.1.2(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(axios@1.7.7)(rxjs@7.8.1)': + dependencies: + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + axios: 1.7.7 + rxjs: 7.8.1 + '@nestjs/class-validator@0.13.1': dependencies: '@types/validator': 13.12.2 @@ -4660,15 +4685,16 @@ snapshots: '@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1)': dependencies: - class-transformer: 0.5.1 - class-validator: 0.14.1 iterare: 1.2.1 reflect-metadata: 0.2.0 rxjs: 7.8.1 tslib: 2.5.3 uid: 2.0.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.1 - '@nestjs/config@3.3.0(@nestjs/common@10.0.0)(rxjs@7.8.1)': + '@nestjs/config@3.3.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(rxjs@7.8.1)': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) dotenv: 16.4.5 @@ -4676,10 +4702,9 @@ snapshots: lodash: 4.17.21 rxjs: 7.8.1 - '@nestjs/core@10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)': + '@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0) '@nuxtjs/opencollective': 0.3.2 fast-safe-stringify: 2.1.1 iterare: 1.2.1 @@ -4688,26 +4713,29 @@ snapshots: rxjs: 7.8.1 tslib: 2.5.3 uid: 2.0.2 + optionalDependencies: + '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) transitivePeerDependencies: - encoding - '@nestjs/jwt@10.2.0(@nestjs/common@10.0.0)': + '@nestjs/jwt@10.2.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) '@types/jsonwebtoken': 9.0.5 jsonwebtoken: 9.0.2 - '@nestjs/mapped-types@2.0.6(@nestjs/common@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)': + '@nestjs/mapped-types@2.0.6(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + reflect-metadata: 0.2.0 + optionalDependencies: class-transformer: 0.5.1 class-validator: 0.14.1 - reflect-metadata: 0.2.0 - '@nestjs/platform-express@10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)': + '@nestjs/platform-express@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) body-parser: 1.20.2 cors: 2.8.5 express: 4.18.2 @@ -4727,34 +4755,36 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/swagger@8.0.5(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)': + '@nestjs/swagger@8.0.5(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)': dependencies: '@microsoft/tsdoc': 0.15.0 '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/mapped-types': 2.0.6(@nestjs/common@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) - class-transformer: 0.5.1 - class-validator: 0.14.1 + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/mapped-types': 2.0.6(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) js-yaml: 4.1.0 lodash: 4.17.21 path-to-regexp: 3.3.0 reflect-metadata: 0.2.0 swagger-ui-dist: 5.18.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.1 - '@nestjs/testing@10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0)': + '@nestjs/testing@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0)': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) tslib: 2.5.3 + optionalDependencies: + '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) - '@nestjs/typeorm@10.0.2(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)(typeorm@0.3.20)': + '@nestjs/typeorm@10.0.2(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)))': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) reflect-metadata: 0.2.0 rxjs: 7.8.1 - typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1) + typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) uuid: 9.0.1 '@nodelib/fs.scandir@2.1.5': @@ -5280,7 +5310,7 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0)(eslint@8.0.0)(typescript@5.1.3)': + '@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0(eslint@8.0.0)(typescript@5.1.3))(eslint@8.0.0)(typescript@5.1.3)': dependencies: '@eslint-community/regexpp': 4.12.1 '@typescript-eslint/parser': 8.0.0(eslint@8.0.0)(typescript@5.1.3) @@ -5293,6 +5323,7 @@ snapshots: ignore: 5.3.2 natural-compare: 1.4.0 ts-api-utils: 1.4.0(typescript@5.1.3) + optionalDependencies: typescript: 5.1.3 transitivePeerDependencies: - supports-color @@ -5305,6 +5336,7 @@ snapshots: '@typescript-eslint/visitor-keys': 8.0.0 debug: 4.3.7 eslint: 8.0.0 + optionalDependencies: typescript: 5.1.3 transitivePeerDependencies: - supports-color @@ -5320,6 +5352,7 @@ snapshots: '@typescript-eslint/utils': 8.0.0(eslint@8.0.0)(typescript@5.1.3) debug: 4.3.7 ts-api-utils: 1.4.0(typescript@5.1.3) + optionalDependencies: typescript: 5.1.3 transitivePeerDependencies: - eslint @@ -5337,6 +5370,7 @@ snapshots: minimatch: 9.0.5 semver: 7.6.3 ts-api-utils: 1.4.0(typescript@5.1.3) + optionalDependencies: typescript: 5.1.3 transitivePeerDependencies: - supports-color @@ -5457,7 +5491,7 @@ snapshots: acorn@8.14.0: {} ajv-formats@2.1.1(ajv@8.12.0): - dependencies: + optionalDependencies: ajv: 8.12.0 ajv-keywords@3.5.2(ajv@6.12.6): @@ -5533,6 +5567,14 @@ snapshots: aws-ssl-profiles@1.1.2: {} + axios@1.7.7: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.1 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + babel-jest@29.7.0(@babel/core@7.26.0): dependencies: '@babel/core': 7.26.0 @@ -5842,13 +5884,13 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 - create-jest@29.7.0(@types/node@20.3.1)(ts-node@10.9.1): + create-jest@29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) + jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -5992,13 +6034,15 @@ snapshots: dependencies: eslint: 8.0.0 - eslint-plugin-prettier@5.0.0(eslint-config-prettier@9.0.0)(eslint@8.0.0)(prettier@3.0.0): + eslint-plugin-prettier@5.0.0(@types/eslint@9.6.1)(eslint-config-prettier@9.0.0(eslint@8.0.0))(eslint@8.0.0)(prettier@3.0.0): dependencies: eslint: 8.0.0 - eslint-config-prettier: 9.0.0(eslint@8.0.0) prettier: 3.0.0 prettier-linter-helpers: 1.0.0 synckit: 0.8.8 + optionalDependencies: + '@types/eslint': 9.6.1 + eslint-config-prettier: 9.0.0(eslint@8.0.0) eslint-scope@5.1.1: dependencies: @@ -6233,6 +6277,8 @@ snapshots: flatted@3.3.1: {} + follow-redirects@1.15.9: {} + foreground-child@3.3.0: dependencies: cross-spawn: 7.0.5 @@ -6592,16 +6638,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.3.1)(ts-node@10.9.1): + jest-cli@29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.1) + '@jest/core': 29.7.0(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) + create-jest: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) + jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -6611,12 +6657,11 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.3.1)(ts-node@10.9.1): + jest-config@29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): dependencies: '@babel/core': 7.26.0 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.3.1 babel-jest: 29.7.0(@babel/core@7.26.0) chalk: 4.1.2 ci-info: 3.9.0 @@ -6636,6 +6681,8 @@ snapshots: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.3.1 ts-node: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) transitivePeerDependencies: - babel-plugin-macros @@ -6718,7 +6765,7 @@ snapshots: jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): - dependencies: + optionalDependencies: jest-resolve: 29.7.0 jest-regex-util@29.6.3: {} @@ -6862,12 +6909,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.5.0(@types/node@20.3.1)(ts-node@10.9.1): + jest@29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.1) + '@jest/core': 29.7.0(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) + jest-cli: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -7350,6 +7397,8 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + proxy-from-env@1.1.0: {} + pump@3.0.2: dependencies: end-of-stream: 1.4.4 @@ -7693,15 +7742,6 @@ snapshots: terser: 5.36.0 webpack: 5.87.0 - terser-webpack-plugin@5.3.10(webpack@5.96.1): - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - jest-worker: 27.5.1 - schema-utils: 3.3.0 - serialize-javascript: 6.0.2 - terser: 5.36.0 - webpack: 5.96.1 - terser@5.36.0: dependencies: '@jridgewell/source-map': 0.3.6 @@ -7747,12 +7787,11 @@ snapshots: dependencies: typescript: 5.1.3 - ts-jest@29.1.0(@babel/core@7.26.0)(jest@29.5.0)(typescript@5.1.3): + ts-jest@29.1.0(@babel/core@7.26.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)))(typescript@5.1.3): dependencies: - '@babel/core': 7.26.0 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.5.0(@types/node@20.3.1)(ts-node@10.9.1) + jest: 29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -7760,15 +7799,19 @@ snapshots: semver: 7.6.3 typescript: 5.1.3 yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.26.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.0) - ts-loader@9.4.3(typescript@5.1.3)(webpack@5.96.1): + ts-loader@9.4.3(typescript@5.1.3)(webpack@5.87.0): dependencies: chalk: 4.1.2 enhanced-resolve: 5.17.1 micromatch: 4.0.8 semver: 7.6.3 typescript: 5.1.3 - webpack: 5.96.1 + webpack: 5.87.0 ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3): dependencies: @@ -7821,7 +7864,7 @@ snapshots: typedarray@0.0.6: {} - typeorm-extension@3.6.3(typeorm@0.3.20): + typeorm-extension@3.6.3(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3))): dependencies: '@faker-js/faker': 8.4.1 consola: 3.2.3 @@ -7831,10 +7874,10 @@ snapshots: rapiq: 0.9.0 reflect-metadata: 0.2.2 smob: 1.5.0 - typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1) + typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) yargs: 17.7.2 - typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1): + typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): dependencies: '@sqltools/formatter': 1.2.5 app-root-path: 3.1.0 @@ -7846,14 +7889,15 @@ snapshots: dotenv: 16.4.5 glob: 10.4.5 mkdirp: 2.1.6 - mysql2: 3.11.4 - pg: 8.13.1 reflect-metadata: 0.2.2 sha.js: 2.4.11 - ts-node: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) tslib: 2.8.1 uuid: 9.0.1 yargs: 17.7.2 + optionalDependencies: + mysql2: 3.11.4 + pg: 8.13.1 + ts-node: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) transitivePeerDependencies: - supports-color @@ -7947,36 +7991,6 @@ snapshots: - esbuild - uglify-js - webpack@5.96.1: - dependencies: - '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.6 - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/wasm-edit': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.14.0 - browserslist: 4.24.2 - chrome-trace-event: 1.0.4 - enhanced-resolve: 5.17.1 - es-module-lexer: 1.5.4 - eslint-scope: 5.1.1 - events: 3.3.0 - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.0 - mime-types: 2.1.35 - neo-async: 2.6.2 - schema-utils: 3.3.0 - tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(webpack@5.96.1) - watchpack: 2.4.2 - webpack-sources: 3.2.3 - transitivePeerDependencies: - - '@swc/core' - - esbuild - - uglify-js - whatwg-url@5.0.0: dependencies: tr46: 0.0.3 diff --git a/src/chat-message/chat-message.controller.ts b/src/chat-message/chat-message.controller.ts index 02fd60d..1f9b594 100644 --- a/src/chat-message/chat-message.controller.ts +++ b/src/chat-message/chat-message.controller.ts @@ -6,6 +6,7 @@ import { HttpCode, HttpStatus, Injectable, + InternalServerErrorException, Param, ParseUUIDPipe, Patch, @@ -21,13 +22,23 @@ import { Role } from 'src/shared/enums'; import { CreateChatMessageDto, ChatMessageResponseDto } from './dtos'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { CreateChatMessageGuard } from './guards/create-chat-message.guard'; +import { HttpService } from '@nestjs/axios'; +import { catchError } from 'rxjs'; +import { AxiosError } from 'axios'; +import { ConfigService } from '@nestjs/config'; +import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; +import { QADto } from './dtos/qa.dto'; @Controller('chat-message') @Injectable() @ApiTags('Chat Message') @ApiBearerAuth() export class ChatMessageController { - constructor(private readonly chatMessageService: ChatMessageService) {} + constructor( + private readonly chatMessageService: ChatMessageService, + private readonly httpService: HttpService, + private readonly configService: ConfigService, + ) {} @Get(':id') @ApiResponse({ @@ -62,10 +73,24 @@ export class ChatMessageController { @Body() createChatMessageDto: CreateChatMessageDto, @Req() request: AuthenticatedRequest, ) { + const chatMessages = await this.chatMessageService.findMany({ + chatRoom: { id: createChatMessageDto.chatRoomId }, + }); const chatMessage = await this.chatMessageService.create( request.user.id, createChatMessageDto, ); + const response = this.httpService + .post( + `${this.configService.getOrThrow(GLOBAL_CONFIG.AI_URL)}/qa`, + new QADto('chapterSummary', chatMessages), + ) + .pipe( + catchError((error: AxiosError) => { + throw new InternalServerErrorException(error.message); + }), + ); + console.log(response); return new ChatMessageResponseDto(chatMessage); } diff --git a/src/chat-message/chat-message.module.ts b/src/chat-message/chat-message.module.ts index 60115be..b0c61c4 100644 --- a/src/chat-message/chat-message.module.ts +++ b/src/chat-message/chat-message.module.ts @@ -7,6 +7,7 @@ import { ChatMessageService } from './chat-message.service'; import { chatMessageProviders } from './chat-message.providers'; import { ChatRoomModule } from 'src/chat-room/chat-room.module'; import { EnrollmentModule } from 'src/enrollment/enrollment.module'; +import { HttpModule } from '@nestjs/axios'; @Module({ imports: [ @@ -14,6 +15,7 @@ import { EnrollmentModule } from 'src/enrollment/enrollment.module'; TypeOrmModule.forFeature([ChatMessage]), forwardRef(() => ChatRoomModule), EnrollmentModule, + HttpModule, ], controllers: [ChatMessageController], providers: [...chatMessageProviders, ChatMessageService], diff --git a/src/chat-message/chat-message.service.ts b/src/chat-message/chat-message.service.ts index b84a150..f7f154f 100644 --- a/src/chat-message/chat-message.service.ts +++ b/src/chat-message/chat-message.service.ts @@ -39,6 +39,21 @@ export class ChatMessageService { } } + async findMany(where: FindOptionsWhere): Promise { + try { + return await this.chatMessageRepository.find({ + where, + relations: { + user: true, + chatRoom: true, + }, + }); + } catch (error) { + if (error instanceof Error) + throw new InternalServerErrorException(error.message); + } + } + async findAll({ where, page = 1, diff --git a/src/chat-message/dtos/qa.dto.ts b/src/chat-message/dtos/qa.dto.ts new file mode 100644 index 0000000..51d4d92 --- /dev/null +++ b/src/chat-message/dtos/qa.dto.ts @@ -0,0 +1,21 @@ +import { Role } from 'src/shared/enums'; +import { ChatMessage } from '../chat-message.entity'; + +export class QADto { + chapter_summary: string; + chat_history: any[]; + + constructor(chapterSummary: string, chatMessage: ChatMessage[]) { + this.chapter_summary = chapterSummary; + this.chat_history = chatMessage.map((chat) => { + if (chat.user.role === Role.ADMIN) { + return { + agent: chat.content, + }; + } + return { + user: chat.content, + }; + }); + } +} diff --git a/src/shared/configs/dotenv.config.ts b/src/shared/configs/dotenv.config.ts index 7c8e00c..878a256 100644 --- a/src/shared/configs/dotenv.config.ts +++ b/src/shared/configs/dotenv.config.ts @@ -27,4 +27,5 @@ export const dotenvConfig = Joi.object({ AWS_BUCKET_NAME: Joi.string().required(), ADMIN_EMAIL: Joi.string().required(), ADMIN_PASSWORD: Joi.string().required(), + AI_URL: Joi.string().required(), }); diff --git a/src/shared/constants/global-config.constant.ts b/src/shared/constants/global-config.constant.ts index 74dae04..f9d9202 100644 --- a/src/shared/constants/global-config.constant.ts +++ b/src/shared/constants/global-config.constant.ts @@ -18,4 +18,5 @@ export const GLOBAL_CONFIG = { AWS_BUCKET_NAME: 'AWS_BUCKET_NAME', ADMIN_EMAIL: 'ADMIN_EMAIL', ADMIN_PASSWORD: 'ADMIN_PASSWORD', + AI_URL: 'AI_URL', }; diff --git a/src/user-streak/user-streak.module.ts b/src/user-streak/user-streak.module.ts index 458d084..b97a16b 100644 --- a/src/user-streak/user-streak.module.ts +++ b/src/user-streak/user-streak.module.ts @@ -5,9 +5,10 @@ import { userStreakProviders } from './user-streak.providers'; import { UserStreakService } from './user-streak.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { UserStreak } from './user-streak.entity'; +import { UserModule } from 'src/user/user.module'; @Module({ - imports: [DatabaseModule, TypeOrmModule.forFeature([UserStreak])], + imports: [DatabaseModule, TypeOrmModule.forFeature([UserStreak]), UserModule], controllers: [UserStreakController], providers: [...userStreakProviders, UserStreakService], exports: [UserStreakService], diff --git a/src/user-streak/user-streak.service.ts b/src/user-streak/user-streak.service.ts index 36922d0..a0b4c08 100644 --- a/src/user-streak/user-streak.service.ts +++ b/src/user-streak/user-streak.service.ts @@ -6,18 +6,28 @@ import { } from '@nestjs/common'; import { FindOneOptions, FindOptionsWhere, Repository } from 'typeorm'; import { UserStreak } from './user-streak.entity'; +import { UserService } from 'src/user/user.service'; @Injectable() export class UserStreakService { constructor( @Inject('UserStreakRepository') private readonly userStreakRepository: Repository, + private readonly userService: UserService, ) {} async create(userId: string): Promise { - return await this.userStreakRepository.save({ + const userStreak = await this.userStreakRepository.save({ user: { id: userId }, }); + await this.userService.increment( + { + id: userId, + }, + 'points', + 5, + ); + return userStreak; } async findAll(): Promise { diff --git a/src/user/user.service.ts b/src/user/user.service.ts index ea34624..91d7cd0 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -92,4 +92,16 @@ export class UserService implements OnModuleInit { if (error instanceof Error) throw new NotFoundException('User not found'); } } + + async increment( + where: FindOptionsWhere, + propertyPath: string, + value: number, + ): Promise { + try { + await this.userRepository.increment(where, propertyPath, value); + } catch { + throw new NotFoundException('User not found'); + } + } } From 07802ceed725e7ffec31a6dc3f8c7741c2286f71 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Mon, 25 Nov 2024 12:16:35 +0700 Subject: [PATCH 105/155] feat: connect with ai --- package.json | 2 + pnpm-lock.yaml | 266 ++++++++++++++------------- src/enrollment/enrollment.service.ts | 7 + src/exam/dtos/pretest.dto.ts | 79 ++++++++ src/exam/exam.controller.ts | 3 +- src/exam/exam.module.ts | 6 + src/exam/exam.service.ts | 93 ++++++---- src/user/dtos/user-response.dto.ts | 1 + 8 files changed, 293 insertions(+), 164 deletions(-) create mode 100644 src/exam/dtos/pretest.dto.ts diff --git a/package.json b/package.json index ecd4a51..8183d6c 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ }, "dependencies": { "@aws-sdk/client-s3": "^3.693.0", + "@nestjs/axios": "^3.1.2", "@nestjs/class-validator": "0.13.1", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.3.0", @@ -37,6 +38,7 @@ "@nestjs/typeorm": "^10.0.2", "@smithy/types": "^3.7.1", "argon2": "^0.41.1", + "axios": "^1.7.7", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dotenv": "^16.4.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e3d564b..560d1e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@aws-sdk/client-s3': specifier: ^3.693.0 version: 3.693.0 + '@nestjs/axios': + specifier: ^3.1.2 + version: 3.1.2(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(axios@1.7.7)(rxjs@7.8.1) '@nestjs/class-validator': specifier: 0.13.1 version: 0.13.1 @@ -19,31 +22,34 @@ importers: version: 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) '@nestjs/config': specifier: ^3.3.0 - version: 3.3.0(@nestjs/common@10.0.0)(rxjs@7.8.1) + version: 3.3.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(rxjs@7.8.1) '@nestjs/core': specifier: ^10.0.0 - version: 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + version: 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) '@nestjs/jwt': specifier: ^10.2.0 - version: 10.2.0(@nestjs/common@10.0.0) + version: 10.2.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1)) '@nestjs/mapped-types': specifier: ^2.0.6 - version: 2.0.6(@nestjs/common@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) + version: 2.0.6(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) '@nestjs/platform-express': specifier: ^10.0.0 - version: 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0) + version: 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) '@nestjs/swagger': specifier: ^8.0.5 - version: 8.0.5(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) + version: 8.0.5(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) '@nestjs/typeorm': specifier: ^10.0.2 - version: 10.0.2(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)(typeorm@0.3.20) + version: 10.0.2(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3))) '@smithy/types': specifier: ^3.7.1 version: 3.7.1 argon2: specifier: ^0.41.1 version: 0.41.1 + axios: + specifier: ^1.7.7 + version: 1.7.7 class-transformer: specifier: ^0.5.1 version: 0.5.1 @@ -70,10 +76,10 @@ importers: version: 7.8.1 typeorm: specifier: ^0.3.20 - version: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1) + version: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) typeorm-extension: specifier: ^3.6.3 - version: 3.6.3(typeorm@0.3.20) + version: 3.6.3(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3))) devDependencies: '@nestjs/cli': specifier: ^10.0.0 @@ -83,7 +89,7 @@ importers: version: 10.0.0(chokidar@3.5.3)(typescript@5.1.3) '@nestjs/testing': specifier: ^10.0.0 - version: 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0) + version: 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0) '@types/express': specifier: ^5.0.0 version: 5.0.0 @@ -101,7 +107,7 @@ importers: version: 6.0.0 '@typescript-eslint/eslint-plugin': specifier: ^8.0.0 - version: 8.0.0(@typescript-eslint/parser@8.0.0)(eslint@8.0.0)(typescript@5.1.3) + version: 8.0.0(@typescript-eslint/parser@8.0.0(eslint@8.0.0)(typescript@5.1.3))(eslint@8.0.0)(typescript@5.1.3) '@typescript-eslint/parser': specifier: ^8.0.0 version: 8.0.0(eslint@8.0.0)(typescript@5.1.3) @@ -113,10 +119,10 @@ importers: version: 9.0.0(eslint@8.0.0) eslint-plugin-prettier: specifier: ^5.0.0 - version: 5.0.0(eslint-config-prettier@9.0.0)(eslint@8.0.0)(prettier@3.0.0) + version: 5.0.0(@types/eslint@9.6.1)(eslint-config-prettier@9.0.0(eslint@8.0.0))(eslint@8.0.0)(prettier@3.0.0) jest: specifier: ^29.5.0 - version: 29.5.0(@types/node@20.3.1)(ts-node@10.9.1) + version: 29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) prettier: specifier: ^3.0.0 version: 3.0.0 @@ -128,10 +134,10 @@ importers: version: 7.0.0 ts-jest: specifier: ^29.1.0 - version: 29.1.0(@babel/core@7.26.0)(jest@29.5.0)(typescript@5.1.3) + version: 29.1.0(@babel/core@7.26.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)))(typescript@5.1.3) ts-loader: specifier: ^9.4.3 - version: 9.4.3(typescript@5.1.3)(webpack@5.96.1) + version: 9.4.3(typescript@5.1.3)(webpack@5.87.0) ts-node: specifier: ^10.9.1 version: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) @@ -640,6 +646,13 @@ packages: '@microsoft/tsdoc@0.15.0': resolution: {integrity: sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==} + '@nestjs/axios@3.1.2': + resolution: {integrity: sha512-pFlfi4ZQsZtTNNhvgssbxjCHUd1nMpV3sXy/xOOB2uEJhw3M8j8SFR08gjFNil2we2Har7VCsXLfCkwbMHECFQ==} + peerDependencies: + '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + axios: ^1.3.1 + rxjs: ^6.0.0 || ^7.0.0 + '@nestjs/class-validator@0.13.1': resolution: {integrity: sha512-Wwpg9KuGnkWOFleBPEFdHQKwqZsF/PFWQaeSaXwatkofgqD7N6AV5J30xIVgScDP+IcE+MdPGZJ38vHQ24KxPQ==} @@ -1359,6 +1372,9 @@ packages: resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} engines: {node: '>= 6.0.0'} + axios@1.7.7: + resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} + babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1963,6 +1979,15 @@ packages: flatted@3.3.1: resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + foreground-child@3.3.0: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} @@ -2924,6 +2949,9 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + pump@3.0.2: resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} @@ -3549,16 +3577,6 @@ packages: webpack-cli: optional: true - webpack@5.96.1: - resolution: {integrity: sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==} - engines: {node: '>=10.13.0'} - hasBin: true - peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true - whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -3645,10 +3663,11 @@ snapshots: dependencies: ajv: 8.12.0 ajv-formats: 2.1.1(ajv@8.12.0) - chokidar: 3.5.3 jsonc-parser: 3.2.0 rxjs: 7.8.1 source-map: 0.7.4 + optionalDependencies: + chokidar: 3.5.3 '@angular-devkit/schematics-cli@16.1.0(chokidar@3.5.3)': dependencies: @@ -3726,7 +3745,7 @@ snapshots: '@aws-sdk/client-sso-oidc': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/client-sts': 3.693.0 '@aws-sdk/core': 3.693.0 - '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) '@aws-sdk/middleware-bucket-endpoint': 3.693.0 '@aws-sdk/middleware-expect-continue': 3.693.0 '@aws-sdk/middleware-flexible-checksums': 3.693.0 @@ -3787,7 +3806,7 @@ snapshots: '@aws-crypto/sha256-js': 5.2.0 '@aws-sdk/client-sts': 3.693.0 '@aws-sdk/core': 3.693.0 - '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) '@aws-sdk/middleware-host-header': 3.693.0 '@aws-sdk/middleware-logger': 3.693.0 '@aws-sdk/middleware-recursion-detection': 3.693.0 @@ -3875,7 +3894,7 @@ snapshots: '@aws-crypto/sha256-js': 5.2.0 '@aws-sdk/client-sso-oidc': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/core': 3.693.0 - '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) '@aws-sdk/middleware-host-header': 3.693.0 '@aws-sdk/middleware-logger': 3.693.0 '@aws-sdk/middleware-recursion-detection': 3.693.0 @@ -3949,14 +3968,14 @@ snapshots: '@smithy/util-stream': 3.3.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0)': + '@aws-sdk/credential-provider-ini@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0)': dependencies: '@aws-sdk/client-sts': 3.693.0 '@aws-sdk/core': 3.693.0 '@aws-sdk/credential-provider-env': 3.693.0 '@aws-sdk/credential-provider-http': 3.693.0 '@aws-sdk/credential-provider-process': 3.693.0 - '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0) + '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)) '@aws-sdk/credential-provider-web-identity': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/types': 3.692.0 '@smithy/credential-provider-imds': 3.2.7 @@ -3968,13 +3987,13 @@ snapshots: - '@aws-sdk/client-sso-oidc' - aws-crt - '@aws-sdk/credential-provider-node@3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0)': + '@aws-sdk/credential-provider-node@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0)': dependencies: '@aws-sdk/credential-provider-env': 3.693.0 '@aws-sdk/credential-provider-http': 3.693.0 - '@aws-sdk/credential-provider-ini': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-ini': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) '@aws-sdk/credential-provider-process': 3.693.0 - '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0) + '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)) '@aws-sdk/credential-provider-web-identity': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/types': 3.692.0 '@smithy/credential-provider-imds': 3.2.7 @@ -3996,11 +4015,11 @@ snapshots: '@smithy/types': 3.7.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.693.0(@aws-sdk/client-sso-oidc@3.693.0)': + '@aws-sdk/credential-provider-sso@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))': dependencies: '@aws-sdk/client-sso': 3.693.0 '@aws-sdk/core': 3.693.0 - '@aws-sdk/token-providers': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0) + '@aws-sdk/token-providers': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)) '@aws-sdk/types': 3.692.0 '@smithy/property-provider': 3.1.10 '@smithy/shared-ini-file-loader': 3.1.11 @@ -4129,7 +4148,7 @@ snapshots: '@smithy/types': 3.7.1 tslib: 2.8.1 - '@aws-sdk/token-providers@3.693.0(@aws-sdk/client-sso-oidc@3.693.0)': + '@aws-sdk/token-providers@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))': dependencies: '@aws-sdk/client-sso-oidc': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/types': 3.692.0 @@ -4439,7 +4458,7 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(ts-node@10.9.1)': + '@jest/core@29.7.0(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -4453,7 +4472,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) + jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -4623,6 +4642,12 @@ snapshots: '@microsoft/tsdoc@0.15.0': {} + '@nestjs/axios@3.1.2(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(axios@1.7.7)(rxjs@7.8.1)': + dependencies: + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + axios: 1.7.7 + rxjs: 7.8.1 + '@nestjs/class-validator@0.13.1': dependencies: '@types/validator': 13.12.2 @@ -4660,15 +4685,16 @@ snapshots: '@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1)': dependencies: - class-transformer: 0.5.1 - class-validator: 0.14.1 iterare: 1.2.1 reflect-metadata: 0.2.0 rxjs: 7.8.1 tslib: 2.5.3 uid: 2.0.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.1 - '@nestjs/config@3.3.0(@nestjs/common@10.0.0)(rxjs@7.8.1)': + '@nestjs/config@3.3.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(rxjs@7.8.1)': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) dotenv: 16.4.5 @@ -4676,10 +4702,9 @@ snapshots: lodash: 4.17.21 rxjs: 7.8.1 - '@nestjs/core@10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)': + '@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0) '@nuxtjs/opencollective': 0.3.2 fast-safe-stringify: 2.1.1 iterare: 1.2.1 @@ -4688,26 +4713,29 @@ snapshots: rxjs: 7.8.1 tslib: 2.5.3 uid: 2.0.2 + optionalDependencies: + '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) transitivePeerDependencies: - encoding - '@nestjs/jwt@10.2.0(@nestjs/common@10.0.0)': + '@nestjs/jwt@10.2.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) '@types/jsonwebtoken': 9.0.5 jsonwebtoken: 9.0.2 - '@nestjs/mapped-types@2.0.6(@nestjs/common@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)': + '@nestjs/mapped-types@2.0.6(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + reflect-metadata: 0.2.0 + optionalDependencies: class-transformer: 0.5.1 class-validator: 0.14.1 - reflect-metadata: 0.2.0 - '@nestjs/platform-express@10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)': + '@nestjs/platform-express@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) body-parser: 1.20.2 cors: 2.8.5 express: 4.18.2 @@ -4727,34 +4755,36 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/swagger@8.0.5(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)': + '@nestjs/swagger@8.0.5(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)': dependencies: '@microsoft/tsdoc': 0.15.0 '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/mapped-types': 2.0.6(@nestjs/common@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) - class-transformer: 0.5.1 - class-validator: 0.14.1 + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/mapped-types': 2.0.6(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) js-yaml: 4.1.0 lodash: 4.17.21 path-to-regexp: 3.3.0 reflect-metadata: 0.2.0 swagger-ui-dist: 5.18.2 + optionalDependencies: + class-transformer: 0.5.1 + class-validator: 0.14.1 - '@nestjs/testing@10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0)': + '@nestjs/testing@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0)': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) tslib: 2.5.3 + optionalDependencies: + '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) - '@nestjs/typeorm@10.0.2(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)(typeorm@0.3.20)': + '@nestjs/typeorm@10.0.2(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)))': dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) reflect-metadata: 0.2.0 rxjs: 7.8.1 - typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1) + typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) uuid: 9.0.1 '@nodelib/fs.scandir@2.1.5': @@ -5280,7 +5310,7 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0)(eslint@8.0.0)(typescript@5.1.3)': + '@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0(eslint@8.0.0)(typescript@5.1.3))(eslint@8.0.0)(typescript@5.1.3)': dependencies: '@eslint-community/regexpp': 4.12.1 '@typescript-eslint/parser': 8.0.0(eslint@8.0.0)(typescript@5.1.3) @@ -5293,6 +5323,7 @@ snapshots: ignore: 5.3.2 natural-compare: 1.4.0 ts-api-utils: 1.4.0(typescript@5.1.3) + optionalDependencies: typescript: 5.1.3 transitivePeerDependencies: - supports-color @@ -5305,6 +5336,7 @@ snapshots: '@typescript-eslint/visitor-keys': 8.0.0 debug: 4.3.7 eslint: 8.0.0 + optionalDependencies: typescript: 5.1.3 transitivePeerDependencies: - supports-color @@ -5320,6 +5352,7 @@ snapshots: '@typescript-eslint/utils': 8.0.0(eslint@8.0.0)(typescript@5.1.3) debug: 4.3.7 ts-api-utils: 1.4.0(typescript@5.1.3) + optionalDependencies: typescript: 5.1.3 transitivePeerDependencies: - eslint @@ -5337,6 +5370,7 @@ snapshots: minimatch: 9.0.5 semver: 7.6.3 ts-api-utils: 1.4.0(typescript@5.1.3) + optionalDependencies: typescript: 5.1.3 transitivePeerDependencies: - supports-color @@ -5457,7 +5491,7 @@ snapshots: acorn@8.14.0: {} ajv-formats@2.1.1(ajv@8.12.0): - dependencies: + optionalDependencies: ajv: 8.12.0 ajv-keywords@3.5.2(ajv@6.12.6): @@ -5533,6 +5567,14 @@ snapshots: aws-ssl-profiles@1.1.2: {} + axios@1.7.7: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.1 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + babel-jest@29.7.0(@babel/core@7.26.0): dependencies: '@babel/core': 7.26.0 @@ -5842,13 +5884,13 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 - create-jest@29.7.0(@types/node@20.3.1)(ts-node@10.9.1): + create-jest@29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) + jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -5992,13 +6034,15 @@ snapshots: dependencies: eslint: 8.0.0 - eslint-plugin-prettier@5.0.0(eslint-config-prettier@9.0.0)(eslint@8.0.0)(prettier@3.0.0): + eslint-plugin-prettier@5.0.0(@types/eslint@9.6.1)(eslint-config-prettier@9.0.0(eslint@8.0.0))(eslint@8.0.0)(prettier@3.0.0): dependencies: eslint: 8.0.0 - eslint-config-prettier: 9.0.0(eslint@8.0.0) prettier: 3.0.0 prettier-linter-helpers: 1.0.0 synckit: 0.8.8 + optionalDependencies: + '@types/eslint': 9.6.1 + eslint-config-prettier: 9.0.0(eslint@8.0.0) eslint-scope@5.1.1: dependencies: @@ -6233,6 +6277,8 @@ snapshots: flatted@3.3.1: {} + follow-redirects@1.15.9: {} + foreground-child@3.3.0: dependencies: cross-spawn: 7.0.5 @@ -6592,16 +6638,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.3.1)(ts-node@10.9.1): + jest-cli@29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.1) + '@jest/core': 29.7.0(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) + create-jest: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) + jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -6611,12 +6657,11 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.3.1)(ts-node@10.9.1): + jest-config@29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): dependencies: '@babel/core': 7.26.0 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.3.1 babel-jest: 29.7.0(@babel/core@7.26.0) chalk: 4.1.2 ci-info: 3.9.0 @@ -6636,6 +6681,8 @@ snapshots: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.3.1 ts-node: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) transitivePeerDependencies: - babel-plugin-macros @@ -6718,7 +6765,7 @@ snapshots: jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): - dependencies: + optionalDependencies: jest-resolve: 29.7.0 jest-regex-util@29.6.3: {} @@ -6862,12 +6909,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.5.0(@types/node@20.3.1)(ts-node@10.9.1): + jest@29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): dependencies: - '@jest/core': 29.7.0(ts-node@10.9.1) + '@jest/core': 29.7.0(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) + jest-cli: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -7350,6 +7397,8 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + proxy-from-env@1.1.0: {} + pump@3.0.2: dependencies: end-of-stream: 1.4.4 @@ -7693,15 +7742,6 @@ snapshots: terser: 5.36.0 webpack: 5.87.0 - terser-webpack-plugin@5.3.10(webpack@5.96.1): - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - jest-worker: 27.5.1 - schema-utils: 3.3.0 - serialize-javascript: 6.0.2 - terser: 5.36.0 - webpack: 5.96.1 - terser@5.36.0: dependencies: '@jridgewell/source-map': 0.3.6 @@ -7747,12 +7787,11 @@ snapshots: dependencies: typescript: 5.1.3 - ts-jest@29.1.0(@babel/core@7.26.0)(jest@29.5.0)(typescript@5.1.3): + ts-jest@29.1.0(@babel/core@7.26.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)))(typescript@5.1.3): dependencies: - '@babel/core': 7.26.0 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.5.0(@types/node@20.3.1)(ts-node@10.9.1) + jest: 29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -7760,15 +7799,19 @@ snapshots: semver: 7.6.3 typescript: 5.1.3 yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.26.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.0) - ts-loader@9.4.3(typescript@5.1.3)(webpack@5.96.1): + ts-loader@9.4.3(typescript@5.1.3)(webpack@5.87.0): dependencies: chalk: 4.1.2 enhanced-resolve: 5.17.1 micromatch: 4.0.8 semver: 7.6.3 typescript: 5.1.3 - webpack: 5.96.1 + webpack: 5.87.0 ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3): dependencies: @@ -7821,7 +7864,7 @@ snapshots: typedarray@0.0.6: {} - typeorm-extension@3.6.3(typeorm@0.3.20): + typeorm-extension@3.6.3(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3))): dependencies: '@faker-js/faker': 8.4.1 consola: 3.2.3 @@ -7831,10 +7874,10 @@ snapshots: rapiq: 0.9.0 reflect-metadata: 0.2.2 smob: 1.5.0 - typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1) + typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) yargs: 17.7.2 - typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1): + typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): dependencies: '@sqltools/formatter': 1.2.5 app-root-path: 3.1.0 @@ -7846,14 +7889,15 @@ snapshots: dotenv: 16.4.5 glob: 10.4.5 mkdirp: 2.1.6 - mysql2: 3.11.4 - pg: 8.13.1 reflect-metadata: 0.2.2 sha.js: 2.4.11 - ts-node: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) tslib: 2.8.1 uuid: 9.0.1 yargs: 17.7.2 + optionalDependencies: + mysql2: 3.11.4 + pg: 8.13.1 + ts-node: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) transitivePeerDependencies: - supports-color @@ -7947,36 +7991,6 @@ snapshots: - esbuild - uglify-js - webpack@5.96.1: - dependencies: - '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.6 - '@webassemblyjs/ast': 1.14.1 - '@webassemblyjs/wasm-edit': 1.14.1 - '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.14.0 - browserslist: 4.24.2 - chrome-trace-event: 1.0.4 - enhanced-resolve: 5.17.1 - es-module-lexer: 1.5.4 - eslint-scope: 5.1.1 - events: 3.3.0 - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.0 - mime-types: 2.1.35 - neo-async: 2.6.2 - schema-utils: 3.3.0 - tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(webpack@5.96.1) - watchpack: 2.4.2 - webpack-sources: 3.2.3 - transitivePeerDependencies: - - '@swc/core' - - esbuild - - uglify-js - whatwg-url@5.0.0: dependencies: tr46: 0.0.3 diff --git a/src/enrollment/enrollment.service.ts b/src/enrollment/enrollment.service.ts index fe26d13..3ae66bf 100644 --- a/src/enrollment/enrollment.service.ts +++ b/src/enrollment/enrollment.service.ts @@ -42,6 +42,13 @@ export class EnrollmentService { return enrollments; } + async findEnrollmentByUserId(userId: string): Promise { + const enrollment = this.enrollmentRepository.find({ + where: { user: { id: userId } }, + }); + return enrollment; + } + async findOne(where: FindOptionsWhere): Promise { const options: FindOneOptions = { where, diff --git a/src/exam/dtos/pretest.dto.ts b/src/exam/dtos/pretest.dto.ts new file mode 100644 index 0000000..169fd49 --- /dev/null +++ b/src/exam/dtos/pretest.dto.ts @@ -0,0 +1,79 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class AnswerAiDto { + @ApiProperty({ + description: 'A answer', + type: String, + example: 'import', + }) + a: string; + + @ApiProperty({ + description: 'B answer', + type: String, + example: 'hi', + }) + b: string; + + @ApiProperty({ + description: 'C answer', + type: String, + example: 'hello', + }) + c: string; + + @ApiProperty({ + description: 'D answer', + type: String, + example: 'delete', + }) + d: string; +} + +export class QuestionAiDto { + @ApiProperty({ + description: 'The question text', + type: String, + example: 'What is this?', + }) + question: string; + + @ApiProperty({ + description: 'Answer options for the question', + type: AnswerAiDto, + example: { + a: 'import', + b: 'hi', + c: 'hello', + d: 'delete', + }, + }) + choices: AnswerAiDto; + + @ApiProperty({ + description: 'The correct answer', + type: String, + example: 'c', + }) + answer: string; +} + +export class PretestDto { + @ApiProperty({ + description: 'All data', + type: [QuestionAiDto], + example: [ + { + question: 'What is this?', + choices: { + a: 'import', + b: 'hi', + c: 'hello', + d: 'delete', + }, + answer: 'c', + }, + ], + }) + data: QuestionAiDto[]; +} diff --git a/src/exam/exam.controller.ts b/src/exam/exam.controller.ts index c4ba519..83a2001 100644 --- a/src/exam/exam.controller.ts +++ b/src/exam/exam.controller.ts @@ -174,6 +174,7 @@ export class ExamController { }) @HttpCode(HttpStatus.CREATED) async createQuestionAndChoice( + @Req() request: AuthenticatedRequest, @Param( 'examId', new ParseUUIDPipe({ @@ -183,6 +184,6 @@ export class ExamController { ) examId: string, ): Promise { - return this.examService.createQuestionAndChoice(examId); + return this.examService.createQuestionAndChoice(examId, request.user.id); } } diff --git a/src/exam/exam.module.ts b/src/exam/exam.module.ts index 5e4e1ae..df23a58 100644 --- a/src/exam/exam.module.ts +++ b/src/exam/exam.module.ts @@ -9,6 +9,9 @@ import { CourseModule } from 'src/course-module/course-module.entity'; import { QuestionModule } from 'src/question/question.module'; import { QuestionOptionModule } from 'src/question-option/question-option.module'; import { ExamAnswerModule } from 'src/exam-answer/exam-answer.module'; +import { HttpModule } from '@nestjs/axios'; +import { UserModule } from 'src/user/user.module'; +import { EnrollmentModule } from 'src/enrollment/enrollment.module'; @Module({ imports: [ @@ -16,6 +19,9 @@ import { ExamAnswerModule } from 'src/exam-answer/exam-answer.module'; TypeOrmModule.forFeature([Exam, CourseModule]), QuestionModule, QuestionOptionModule, + HttpModule, + UserModule, + EnrollmentModule, ], controllers: [ExamController], providers: [...examProviders, ExamService], diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts index 0a7d059..b484fab 100644 --- a/src/exam/exam.service.ts +++ b/src/exam/exam.service.ts @@ -21,6 +21,11 @@ import { UpdateExamDto } from './dtos/update-exam.dto'; import { CourseModule } from 'src/course-module/course-module.entity'; import { QuestionService } from 'src/question/question.service'; import { QuestionOptionService } from 'src/question-option/question-option.service'; +import { HttpService } from '@nestjs/axios'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { UserService } from 'src/user/user.service'; +import { EnrollmentService } from 'src/enrollment/enrollment.service'; +import { PretestDto } from './dtos/pretest.dto'; @Injectable() export class ExamService { @@ -31,6 +36,9 @@ export class ExamService { private readonly courseModuleRepository: Repository, private readonly questionService: QuestionService, private readonly questionOptionService: QuestionOptionService, + private readonly httpService: HttpService, + private readonly userService: UserService, + private readonly enrollService: EnrollmentService, ) {} async findAll( @@ -234,47 +242,58 @@ export class ExamService { } } - async fetchData() { - return { - message: 'Generate comment successful', - data: [ - { - question: "What is the purpose of the 'print()' function in Python?", - choices: { - a: 'To import libraries', - b: 'To create a new variable', - c: 'To display output on the screen', - d: 'To end a loop', - }, - answer: 'c', - }, - { - question: 'What data type is represented by the number 5 in Python?', - choices: { - a: 'String', - b: 'Integer', - c: 'Float', - d: 'Boolean', - }, - answer: 'b', + async fetchData(examId: string, userId: string): Promise { + const api = 'https://ai.edusaig.com/ai'; + const user = await this.userService.findOne({ where: { id: userId } }); + if (!user) throw new NotFoundException('Not Found User'); + const exam = await this.examRepository.findOne({ where: { id: examId } }); + if (!exam) throw new NotFoundException('Not Found this exam'); + const enrollments = await this.enrollService.findEnrollmentByUserId(userId); + try { + const requestBody = { + id: exam.id, + user: { + id: user.id, + email: user.email, + points: user.points, + role: user.role, + createdAt: user.createdAt, + updatedAt: user.updatedAt, + fullname: user.fullname, }, - { - question: - 'What is the difference between a list and a tuple in Python?', - choices: { - a: 'A list is mutable and a tuple is immutable.', - b: 'A list is immutable and a tuple is mutable.', - c: 'Both lists and tuples are mutable.', - d: 'Both lists and tuples are immutable.', - }, - answer: 'a', + occupation: { + id: exam.id, + title: exam.title, + description: exam.description, + createdAt: exam.createdAt, + updatedAt: exam.updatedAt, }, - ], - }; + createdAt: new Date(), + updatedAt: new Date(), + ...(enrollments.length > 0 && { + topics: enrollments.map((enrollment) => ({ + id: enrollment.id, + title: enrollment.course.title, + description: enrollment.course.description, + level: enrollment.course.level, + createdAt: enrollment.createdAt, + updatedAt: enrollment.updatedAt, + })), + }), + }; + + const response = await this.httpService.axiosRef.post( + `${api}/generate-pretest/`, + requestBody, + ); + return { data: response.data }; + } catch (error) { + throw new Error('Failed to fetch data or process request'); + } } - async createQuestionAndChoice(examId: string): Promise { - const fetchData = await this.fetchData(); + async createQuestionAndChoice(examId: string, userId: string): Promise { + const fetchData = await this.fetchData(examId, userId); let orderIndex = (await this.questionService.getMaxOrderIndex(examId)) + 1; await Promise.all( fetchData.data.map(async (data) => { diff --git a/src/user/dtos/user-response.dto.ts b/src/user/dtos/user-response.dto.ts index 45545fc..d52539e 100644 --- a/src/user/dtos/user-response.dto.ts +++ b/src/user/dtos/user-response.dto.ts @@ -58,6 +58,7 @@ export class UserResponseDto { this.id = user.id; this.email = user.email; this.role = user.role; + this.points = user.points; this.createdAt = user.createdAt; this.updatedAt = user.updatedAt; this.fullname = user.fullname; From 5bd9b2afef3b4d251de0af77f4c95dc4ebdf5699 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Mon, 25 Nov 2024 12:43:08 +0700 Subject: [PATCH 106/155] feat: refactor user streak handling, update response DTOs to use Date types, and change update method to create new streaks --- src/auth/auth.service.ts | 4 -- .../dtos/user-streak-response.dto.ts | 26 ++++------- src/user-streak/user-streak.controller.ts | 41 ++++++++++++----- src/user-streak/user-streak.entity.ts | 17 ++++--- src/user-streak/user-streak.service.ts | 45 +++---------------- src/user/user.controller.ts | 4 +- src/user/user.entity.ts | 4 ++ src/user/user.service.ts | 2 +- 8 files changed, 61 insertions(+), 82 deletions(-) diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 2108f66..8918ea7 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -39,8 +39,6 @@ export class AuthService { role: user.role, }); const refreshToken = this.generateRefreshToken(); - if (user.role === Role.STUDENT) - await this.userStreakService.update(user.id); return { accessToken, refreshToken, @@ -68,8 +66,6 @@ export class AuthService { role: createdUser.role, }); const refreshToken = this.generateRefreshToken(); - if (createdUser.role === Role.STUDENT) - await this.userStreakService.create(createdUser.id); return { accessToken, refreshToken, diff --git a/src/user-streak/dtos/user-streak-response.dto.ts b/src/user-streak/dtos/user-streak-response.dto.ts index a8ae2f7..4871bf1 100644 --- a/src/user-streak/dtos/user-streak-response.dto.ts +++ b/src/user-streak/dtos/user-streak-response.dto.ts @@ -11,46 +11,39 @@ export class UserStreakResponseDto { @ApiProperty({ description: 'User current streak', - type: Number, - example: 0, + type: Date, + example: new Date(), }) - currentStreak: number; + currentStreak: Date; @ApiProperty({ description: 'User longest streak', - type: Number, - example: 0, + type: Date, + example: new Date(), }) - longestStreak: number; + longestStreak: Date; @ApiProperty({ description: 'User last activity date', type: Date, - example: '2021-08-01T00:00:00.000Z', + example: new Date(), }) lastActivityDate: Date; @ApiProperty({ description: 'User created date', type: Date, - example: '2021-08-01T00:00:00.000Z', + example: new Date(), }) createdAt: Date; @ApiProperty({ description: 'User updated date', type: Date, - example: '2021-08-01T00:00:00.000Z', + example: new Date(), }) updatedAt: Date; - @ApiProperty({ - description: 'User ID', - type: String, - example: '123e4567-e89b-12d3-a456-426614174000', - }) - userId: string; - constructor(userStreak: UserStreak) { this.id = userStreak.id; this.currentStreak = userStreak.currentStreak; @@ -58,6 +51,5 @@ export class UserStreakResponseDto { this.lastActivityDate = userStreak.lastActivityDate; this.createdAt = userStreak.createdAt; this.updatedAt = userStreak.updatedAt; - this.userId = userStreak.user.id; } } diff --git a/src/user-streak/user-streak.controller.ts b/src/user-streak/user-streak.controller.ts index 2c8668d..77625e5 100644 --- a/src/user-streak/user-streak.controller.ts +++ b/src/user-streak/user-streak.controller.ts @@ -4,7 +4,7 @@ import { HttpCode, HttpStatus, Injectable, - Patch, + Post, Req, } from '@nestjs/common'; import { ApiBearerAuth, ApiResponse, ApiTags } from '@nestjs/swagger'; @@ -14,13 +14,17 @@ import { Role } from 'src/shared/enums/roles.enum'; import { UserStreakResponseDto } from './dtos/user-streak-response.dto'; import { UserStreak } from './user-streak.entity'; import { UserStreakService } from './user-streak.service'; +import { UserService } from 'src/user/user.service'; @Controller('user-streak') @ApiTags('User Streak') @ApiBearerAuth() @Injectable() export class UserStreakController { - constructor(private readonly userStreakService: UserStreakService) {} + constructor( + private readonly userStreakService: UserStreakService, + private readonly userService: UserService, + ) {} @Get() @Roles(Role.ADMIN) @@ -40,26 +44,39 @@ export class UserStreakController { @Get('profile') @ApiResponse({ status: HttpStatus.OK, - type: UserStreak, + type: UserStreakResponseDto, description: 'Get user streak', + isArray: true, }) async findOne( @Req() request: AuthenticatedRequest, - ): Promise { - const userStreak = await this.userStreakService.findOne({ + ): Promise { + const userStreak = await this.userStreakService.findMany({ where: { user: { id: request.user.id } }, relations: { user: true }, }); - return new UserStreakResponseDto(userStreak); + return userStreak.map((userStreak) => new UserStreakResponseDto(userStreak)); } - @Patch() + @Post() @ApiResponse({ - status: HttpStatus.NO_CONTENT, - description: 'Update user streak', + status: HttpStatus.CREATED, + type: UserStreakResponseDto, + description: 'Create user streak', }) - @HttpCode(HttpStatus.NO_CONTENT) - async update(@Req() request: AuthenticatedRequest): Promise { - return await this.userStreakService.update(request.user.id); + @Roles(Role.STUDENT) + @HttpCode(HttpStatus.CREATED) + async create( + @Req() request: AuthenticatedRequest, + ): Promise { + const userStreak = await this.userStreakService.create(request.user.id); + await this.userService.increment( + { + id: request.user.id, + }, + 'points', + 5, + ); + return new UserStreakResponseDto(userStreak); } } diff --git a/src/user-streak/user-streak.entity.ts b/src/user-streak/user-streak.entity.ts index 882fc3e..5fb15ba 100644 --- a/src/user-streak/user-streak.entity.ts +++ b/src/user-streak/user-streak.entity.ts @@ -4,7 +4,7 @@ import { CreateDateColumn, Entity, JoinColumn, - OneToOne, + ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; @@ -14,21 +14,26 @@ export class UserStreak { @PrimaryGeneratedColumn('uuid') id: string; - @OneToOne(() => User) + @ManyToOne(() => User, (user) => user.streaks, { + onDelete: 'CASCADE', + eager: true, + }) @JoinColumn() user: User; @Column({ - default: 0, + default: new Date(), nullable: false, + type: 'timestamp with time zone', }) - currentStreak: number; + currentStreak: Date; @Column({ - default: 0, + default: new Date(), nullable: false, + type: 'timestamp with time zone', }) - longestStreak: number; + longestStreak: Date; @Column({ type: 'timestamp with time zone', diff --git a/src/user-streak/user-streak.service.ts b/src/user-streak/user-streak.service.ts index a0b4c08..38fb620 100644 --- a/src/user-streak/user-streak.service.ts +++ b/src/user-streak/user-streak.service.ts @@ -4,29 +4,20 @@ import { InternalServerErrorException, NotFoundException, } from '@nestjs/common'; -import { FindOneOptions, FindOptionsWhere, Repository } from 'typeorm'; +import { FindManyOptions, FindOneOptions, FindOptionsWhere, Repository } from 'typeorm'; import { UserStreak } from './user-streak.entity'; -import { UserService } from 'src/user/user.service'; @Injectable() export class UserStreakService { constructor( @Inject('UserStreakRepository') private readonly userStreakRepository: Repository, - private readonly userService: UserService, ) {} async create(userId: string): Promise { const userStreak = await this.userStreakRepository.save({ user: { id: userId }, }); - await this.userService.increment( - { - id: userId, - }, - 'points', - 5, - ); return userStreak; } @@ -34,6 +25,10 @@ export class UserStreakService { return this.userStreakRepository.find(); } + async findMany(options: FindManyOptions): Promise { + return this.userStreakRepository.find(options); + } + async findOne(options: FindOneOptions): Promise { const userStreak = await this.userStreakRepository.findOne(options); if (!userStreak) throw new NotFoundException('User streak not found'); @@ -49,34 +44,4 @@ export class UserStreakService { } } } - - async update(userId: string): Promise { - try { - const streak = await this.findOne({ where: { user: { id: userId } } }); - const currentDate = new Date(); - const diff = - Math.abs( - currentDate.getTime() - new Date(streak.lastActivityDate).getTime(), - ) / - (1000 * 60 * 60 * 24); - if (diff > 1) streak.currentStreak = 0; - else streak.currentStreak++; - if (streak.currentStreak > streak.longestStreak) - streak.longestStreak = streak.currentStreak; - await this.userStreakRepository.update( - { - user: { id: userId }, - }, - { - currentStreak: streak.currentStreak, - longestStreak: streak.longestStreak, - lastActivityDate: currentDate, - }, - ); - } catch (error) { - if (error instanceof Error) { - throw new InternalServerErrorException(error.message); - } - } - } } diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index 9bfb668..ccf340a 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -249,8 +249,8 @@ export class UserController { @HttpCode(HttpStatus.NO_CONTENT) async delete( @Req() request: AuthenticatedRequest, - ): Promise<{ massage: string }> { + ): Promise { + console.log(request.user.id); await this.userService.delete({ id: request.user.id }); - return { massage: 'User deleted successfully' }; } } diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index a6fe352..5d9925d 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -4,6 +4,7 @@ import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; import { Roadmap } from 'src/roadmap/roadmap.entity'; import { Role } from 'src/shared/enums/roles.enum'; import { UserBackground } from 'src/user-background/user-background.entity'; +import { UserStreak } from 'src/user-streak/user-streak.entity'; import { UserReward } from 'src/userReward/user-reward.entity'; import { Column, @@ -44,6 +45,9 @@ export class User { @OneToMany(() => Roadmap, (roadmap) => roadmap.user) roadmaps: Roadmap[]; + @OneToMany(() => UserStreak, (streak) => streak.user) + streaks: UserStreak[]; + @Column({ nullable: false, unique: true, diff --git a/src/user/user.service.ts b/src/user/user.service.ts index 91d7cd0..48d3368 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -89,7 +89,7 @@ export class UserService implements OnModuleInit { try { await this.userRepository.delete(criteria); } catch (error) { - if (error instanceof Error) throw new NotFoundException('User not found'); + if (error instanceof Error) throw new NotFoundException(error.message); } } From 702ee24a498c6629ed9cd9af270fb013f4905751 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Mon, 25 Nov 2024 18:21:24 +0700 Subject: [PATCH 107/155] refactor: remove console.log statements from chat message and user controllers --- src/chat-message/chat-message.controller.ts | 1 - src/user/user.controller.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/chat-message/chat-message.controller.ts b/src/chat-message/chat-message.controller.ts index 1f9b594..e802f2e 100644 --- a/src/chat-message/chat-message.controller.ts +++ b/src/chat-message/chat-message.controller.ts @@ -90,7 +90,6 @@ export class ChatMessageController { throw new InternalServerErrorException(error.message); }), ); - console.log(response); return new ChatMessageResponseDto(chatMessage); } diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index ccf340a..5c5f667 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -250,7 +250,6 @@ export class UserController { async delete( @Req() request: AuthenticatedRequest, ): Promise { - console.log(request.user.id); await this.userService.delete({ id: request.user.id }); } } From 22bb5011a68f73a0eb190769ca74f3a6d41a1c61 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Mon, 25 Nov 2024 19:13:03 +0700 Subject: [PATCH 108/155] refactor: change BadRequestException to ConflictException for existing enrollment check --- src/enrollment/enrollment.service.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/enrollment/enrollment.service.ts b/src/enrollment/enrollment.service.ts index 3ae66bf..764d5e9 100644 --- a/src/enrollment/enrollment.service.ts +++ b/src/enrollment/enrollment.service.ts @@ -1,5 +1,5 @@ import { - BadRequestException, + ConflictException, Injectable, InternalServerErrorException, NotFoundException, @@ -66,13 +66,14 @@ export class EnrollmentService { } async create(createEnrollmentDto: CreateEnrollmentDto): Promise { - try { - const enrollment = await this.findOne({ + const enrollment = await this.enrollmentRepository.findOne({ + where: { user: { id: createEnrollmentDto.userId }, course: { id: createEnrollmentDto.courseId }, - }); - if (enrollment) - throw new BadRequestException('Enrollment already exists'); + }, + }); + if (enrollment) throw new ConflictException('Enrollment already exists'); + try { const createdEnrollment = this.enrollmentRepository.create(createEnrollmentDto); return await this.enrollmentRepository.save({ From 57c03aa7066ddb9a8271a46560ef8a015eb7c210 Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Mon, 25 Nov 2024 19:30:12 +0700 Subject: [PATCH 109/155] Feat/chapter-module: add ownership features and query enhancements for chapter and course modules --- src/chapter/chapter.controller.ts | 130 ++++++++-- src/chapter/chapter.service.ts | 47 +++- src/course-module/course-module.controller.ts | 79 +++++- src/course-module/course-module.service.ts | 113 ++++++--- src/course/course.controller.ts | 231 +++++++++++++++-- src/course/course.entity.ts | 2 + src/course/course.service.ts | 238 +++++++++++++----- src/course/dtos/create-course.dto.ts | 3 +- 8 files changed, 693 insertions(+), 150 deletions(-) diff --git a/src/chapter/chapter.controller.ts b/src/chapter/chapter.controller.ts index 0e61978..06b51e8 100644 --- a/src/chapter/chapter.controller.ts +++ b/src/chapter/chapter.controller.ts @@ -47,10 +47,10 @@ import { FileService } from 'src/file/file.service'; import { Folder } from 'src/file/enums/folder.enum'; import { FileInterceptor } from '@nestjs/platform-express'; import { EnrollmentService } from 'src/enrollment/enrollment.service'; +import { Public } from 'src/shared/decorators/public.decorator'; @Controller('chapter') @ApiTags('Chapters') -@ApiBearerAuth() @Injectable() export class ChapterController { constructor( @@ -58,7 +58,7 @@ export class ChapterController { private readonly courseModuleService: CourseModuleService, private readonly fileService: FileService, private readonly enrollmentService: EnrollmentService, - ) {} + ) { } @Get(':id/video') @ApiParam({ @@ -66,6 +66,7 @@ export class ChapterController { type: String, description: 'Course id', }) + @ApiBearerAuth() @ApiResponse({ status: HttpStatus.OK, description: 'Get chapter video', @@ -88,7 +89,7 @@ export class ChapterController { { where: { id } }, ); - const file = await this.fileService.get(Folder.CHAPTER_VIDEOS, chapter.videoKey); + const file = await this.fileService.get(Folder.CHAPTER_VIDEOS, chapter.videoKey); return new StreamableFile(file, { disposition: 'inline', type: `video/${chapter.videoKey.split('.').pop()}`, @@ -146,14 +147,12 @@ export class ChapterController { where: { id }, }); if (chapter.videoKey) - await this.fileService.update(Folder.CHAPTER_VIDEOS, chapter.videoKey, file); + await this.fileService.update(Folder.CHAPTER_VIDEOS, chapter.videoKey, file); else { await this.fileService.upload(Folder.CHAPTER_VIDEOS, id, file); } - await this.chapterService.update(id, { videoKey: `${id}.${file.originalname.split('.').pop()}` }); + await this.chapterService.update(id, { videoKey: `${id}.${file.originalname.split('.').pop()}` }); } - - @Get() @ApiResponse({ @@ -174,11 +173,66 @@ export class ChapterController { required: false, description: 'Items per page', }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + @Public() async findAll( - @Req() request: AuthenticatedRequest, @Query() query: PaginateQueryDto, ): Promise { return this.chapterService.findAll({ + page: query.page, + limit: query.limit, + search: query.search + }); + } + @Get('module/:moduleId') + @ApiParam({ + name: 'moduleId', + type: String, + description: 'Module ID', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: ChapterResponseDto, + description: 'Get all chapters by module id', + isArray: true, + }) + @Public() + async findByModuleId( + @Param('moduleId', ParseUUIDPipe) moduleId: string, + ): Promise { + return this.chapterService.findByModuleId(moduleId); + } + + @Get('with-ownership') + @ApiResponse({ + status: HttpStatus.OK, + type: ChapterResponseDto, + description: 'Get all chapters with ownership', + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiBearerAuth() + async findAllWithOwnership( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return this.chapterService.findAllWithOwnership({ page: query.page, limit: query.limit, search: query.search, @@ -186,31 +240,30 @@ export class ChapterController { role: request.user.role, }); } - - @Get(':id/chat-rooms') + @Get('with-ownership/:id') @ApiResponse({ status: HttpStatus.OK, - type: ChatRoomResponseDto, - description: 'Get all chat rooms for a chapter', - isArray: true, + type: ChapterResponseDto, + description: 'Get a chapter by ID', }) @ApiParam({ name: 'id', type: String, description: 'Chapter ID', }) - @Roles(Role.STUDENT) - async getChatRooms( + @ApiBearerAuth() + async findOneWithOwnership( @Req() request: AuthenticatedRequest, @Param('id', ParseUUIDPipe) id: string, - ): Promise { - const chatRooms = await this.chapterService.getChatRooms( - request.user.id, - id, - ); - return chatRooms.map((chatRoom) => new ChatRoomResponseDto(chatRoom)); + ): Promise { + return this.chapterService.findOneWithOwnership(request.user.id, request.user.role, { + where: { id }, + }); } + + + @Get(':id') @ApiResponse({ status: HttpStatus.OK, @@ -222,15 +275,39 @@ export class ChapterController { type: String, description: 'Chapter ID', }) + @Public() async findOne( - @Req() request: AuthenticatedRequest, @Param('id', ParseUUIDPipe) id: string, ): Promise { - return this.chapterService.findOneWithOwnership(request.user.id, request.user.role, { - where: { id }, - }); + return this.chapterService.findOne({ where: { id, isPreview: true } }); } + @Get(':id/chat-rooms') + @ApiResponse({ + status: HttpStatus.OK, + type: ChatRoomResponseDto, + description: 'Get all chat rooms for a chapter', + isArray: true, + }) + @ApiParam({ + name: 'id', + type: String, + description: 'Chapter ID', + }) + @Roles(Role.STUDENT) + @ApiBearerAuth() + async getChatRooms( + @Req() request: AuthenticatedRequest, + @Param('id', ParseUUIDPipe) id: string, + ): Promise { + const chatRooms = await this.chapterService.getChatRooms( + request.user.id, + id, + ); + return chatRooms.map((chatRoom) => new ChatRoomResponseDto(chatRoom)); + } + + @Post() @Roles(Role.TEACHER) @ApiResponse({ @@ -238,6 +315,7 @@ export class ChapterController { type: ChapterResponseDto, description: 'Create a chapter', }) + @ApiBearerAuth() async create( @Req() request: AuthenticatedRequest, @Body() createChapterDto: CreateChapterDto, @@ -263,6 +341,7 @@ export class ChapterController { type: String, description: 'Chapter ID', }) + @ApiBearerAuth() async update( @Req() request: AuthenticatedRequest, @Param('id', ParseUUIDPipe) id: string, @@ -289,6 +368,7 @@ export class ChapterController { type: String, description: 'Chapter ID', }) + @ApiBearerAuth() async remove( @Param('id', ParseUUIDPipe) id: string, ): Promise { diff --git a/src/chapter/chapter.service.ts b/src/chapter/chapter.service.ts index 87e3786..413afd2 100644 --- a/src/chapter/chapter.service.ts +++ b/src/chapter/chapter.service.ts @@ -26,11 +26,38 @@ export class ChapterService { private readonly chatRoomService: ChatRoomService, private readonly enrollmentService: EnrollmentService, ) { } - async findAll({ page = 1, limit = 20, search = '', + }: { + page?: number; + limit?: number; + search?: string; + }): Promise { + const { find } = await createPagination(this.chapterRepository, { + page, + limit, + }); + const baseSearch = { + isPreview: true, + ...(search ? { title: ILike(`%${search}%`) } : {}) + }; + const chapters = await find({ + where: baseSearch, + relations: { + module: true, + }, + + }).run(); + return chapters; + } + + + async findAllWithOwnership({ + page = 1, + limit = 20, + search = '', userId, role, }: { @@ -64,7 +91,23 @@ export class ChapterService { return chapter; } - + async findByModuleId(moduleId: string): Promise { + const chapters = await this.chapterRepository.find({ + where: { + module: { + id: moduleId, + course: { + status: CourseStatus.PUBLISHED + } + } + }, + relations: { + module: true, + }, + }); + if (!chapters) throw new NotFoundException('Chapter not found'); + return chapters; + } async findOneWithOwnership( userId: string, role: Role, diff --git a/src/course-module/course-module.controller.ts b/src/course-module/course-module.controller.ts index 5a12b31..fd5cc11 100644 --- a/src/course-module/course-module.controller.ts +++ b/src/course-module/course-module.controller.ts @@ -32,10 +32,10 @@ import { UpdateCourseModuleDto } from './dtos/update-course-module.dto'; import { CourseService } from 'src/course/course.service'; import { CourseOwnership } from 'src/shared/decorators/course-ownership.decorator'; import { Roles } from 'src/shared/decorators/role.decorator'; +import { Public } from 'src/shared/decorators/public.decorator'; @Controller('course-module') @ApiTags('Course Modules') -@ApiBearerAuth() export class CourseModuleController { constructor( private readonly courseModuleService: CourseModuleService, @@ -46,7 +46,7 @@ export class CourseModuleController { @ApiResponse({ status: HttpStatus.OK, type: CourseModuleResponseDto, - description: 'Get all course modules', + description: 'Get all course', isArray: true, }) @ApiQuery({ @@ -65,22 +65,59 @@ export class CourseModuleController { name: 'search', type: String, required: false, - description: 'Search by title', + description: 'Search by email', }) + @Public() async findAll( - @Req() request: AuthenticatedRequest, @Query() query: PaginateQueryDto, ): Promise { return this.courseModuleService.findAll({ page: query.page, limit: query.limit, search: query.search, + }); + } + + @Get('with-ownership') + @ApiResponse({ + status: HttpStatus.OK, + type: CourseModuleResponseDto, + description: 'Get all course modules with ownership', + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + @ApiBearerAuth() + async findAllWithOwnership( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return this.courseModuleService.findAllWithOwnership({ + page: query.page, + limit: query.limit, + search: query.search, userId: request.user.id, role: request.user.role, }); } - @Get(':id') + @Get('with-ownership/:id') @ApiParam({ name: 'id', type: String, @@ -89,19 +126,36 @@ export class CourseModuleController { @ApiResponse({ status: HttpStatus.OK, type: CourseModuleResponseDto, - description: 'Get a course module', + description: 'Get owned course module', }) - async findOne( + @ApiBearerAuth() + async findOneWithOwnership( @Req() request: AuthenticatedRequest, @Param('id', new ParseUUIDPipe()) id: string, ): Promise { - return this.courseModuleService.findOne( + return this.courseModuleService.findOneWithOwnership( request.user.id, request.user.role, { where: { id } }, ); } + @Get(':id') + @ApiParam({ + name: 'id', + type: String, + description: 'Course Module ID', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: CourseModuleResponseDto, + description: 'Get a course module', + }) + @Public() + async findOne(@Param('id', new ParseUUIDPipe()) id: string): Promise { + return this.courseModuleService.findOne({ where: { id } }); + } + @Get('course/:courseId') @ApiParam({ name: 'courseId', @@ -114,11 +168,10 @@ export class CourseModuleController { description: 'Get course modules by course ID', isArray: true, }) + @Public() async findByCourseId( - @Req() request: AuthenticatedRequest, @Param('courseId', new ParseUUIDPipe()) courseId: string, ): Promise { - await this.courseService.validateOwnership(courseId, request.user.id); return this.courseModuleService.findByCourseId(courseId); } @@ -129,6 +182,7 @@ export class CourseModuleController { type: CourseModuleResponseDto, description: 'Create a course module', }) + @ApiBearerAuth() async create( @Req() request: AuthenticatedRequest, @Body() createCourseModuleDto: CreateCourseModuleDto, @@ -150,6 +204,7 @@ export class CourseModuleController { description: 'Course Module ID', }) @CourseOwnership({ adminDraftOnly: true }) + @ApiBearerAuth() async update( @Req() request: AuthenticatedRequest, @Param( @@ -183,9 +238,13 @@ export class CourseModuleController { type: CourseModuleResponseDto, description: 'Delete a course module', }) + @ApiBearerAuth() async remove( @Param('id', new ParseUUIDPipe()) id: string, ): Promise { return this.courseModuleService.remove(id); } + + + } diff --git a/src/course-module/course-module.service.ts b/src/course-module/course-module.service.ts index 62f52c9..f42b842 100644 --- a/src/course-module/course-module.service.ts +++ b/src/course-module/course-module.service.ts @@ -24,66 +24,66 @@ export class CourseModuleService { page = 1, limit = 20, search = '', - userId, - role, }: { page?: number; limit?: number; search?: string; - userId: string; - role: Role; }): Promise { - const { find } = await createPagination(this.courseModuleRepository, { - page, - limit, - }); - - const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; - const whereCondition = this.buildWhereCondition(userId, role, baseSearch); - + const { find } = await createPagination(this.courseModuleRepository, { page, limit }); + const baseSearch = { + course : { + status: CourseStatus.PUBLISHED + }, + ...(search ? { title: ILike(`%${search}%`) } : {}), + }; const courseModules = await find({ - where: whereCondition, + where: baseSearch, relations: { course: true, }, + }).run(); - - return courseModules; + return courseModules } - async findOne( - userId: string, - role: Role, options: FindOneOptions, ): Promise { - const baseWhere = options.where as FindOptionsWhere; - const whereCondition = this.buildWhereCondition(userId, role, baseWhere); - const courseModule = await this.courseModuleRepository.findOne({ - where: whereCondition, + ...options, relations: { course: true, }, + where: { + ...options.where, + course: { + status: CourseStatus.PUBLISHED + }, + }, }); - - if (!courseModule) { - throw new NotFoundException('Course Module not found'); - } - + + if (!courseModule) throw new NotFoundException('Course Module not found'); return courseModule; } async findByCourseId(courseId: string): Promise { const courseModules = await this.courseModuleRepository.find({ - where: { courseId }, + where: { + course: { + id: courseId, + status: CourseStatus.PUBLISHED + } + }, relations: { course: true, }, }); - + + if (!courseModules.length) { + throw new NotFoundException('Course modules not found or course is not published'); + } + return courseModules; } - async validateAndGetNextOrderIndex(courseId: string): Promise { const existingModules = await this.courseModuleRepository.find({ where: { courseId }, @@ -264,4 +264,57 @@ export class CourseModuleService { return buildCondition(); } + + async findAllWithOwnership({ + page = 1, + limit = 20, + search = '', + userId, + role, + }: { + page?: number; + limit?: number; + search?: string; + userId: string; + role: Role; + }): Promise { + const { find } = await createPagination(this.courseModuleRepository, { + page, + limit, + }); + + const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; + const whereCondition = this.buildWhereCondition(userId, role, baseSearch); + + const courseModules = await find({ + where: whereCondition, + relations: { + course: true, + }, + }).run(); + + return courseModules; + } + + async findOneWithOwnership( + userId: string, + role: Role, + options: FindOneOptions, + ): Promise { + const baseWhere = options.where as FindOptionsWhere; + const whereCondition = this.buildWhereCondition(userId, role, baseWhere); + + const courseModule = await this.courseModuleRepository.findOne({ + where: whereCondition, + relations: { + course: true, + }, + }); + + if (!courseModule) { + throw new NotFoundException('Course Module not found'); + } + + return courseModule; + } } diff --git a/src/course/course.controller.ts b/src/course/course.controller.ts index ebe715a..569e873 100644 --- a/src/course/course.controller.ts +++ b/src/course/course.controller.ts @@ -3,6 +3,7 @@ import { Body, Controller, Delete, + ForbiddenException, Get, HttpCode, HttpStatus, @@ -30,7 +31,7 @@ import { import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { CategoryService } from 'src/category/category.service'; import { Roles } from 'src/shared/decorators/role.decorator'; -import { Role } from 'src/shared/enums'; +import { CourseStatus, Role } from 'src/shared/enums'; import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { CourseService } from './course.service'; import { @@ -47,15 +48,175 @@ import { FileInterceptor } from '@nestjs/platform-express'; @Controller('course') @ApiTags('Course') -@ApiBearerAuth() @Injectable() export class CourseController { constructor( private readonly courseService: CourseService, private readonly categoryService: CategoryService, private readonly fileService: FileService, - ) {} + ) { } + @Get() + @ApiResponse({ + status: HttpStatus.OK, + type: CourseResponseDto, + description: 'Get all course', + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by email', + }) + @Public() + async findAll( + @Query() query: PaginateQueryDto, + ): Promise { + return this.courseService.findAll({ + page: query.page, + limit: query.limit, + search: query.search, + }); + } + @Get('new-arrivals') + @ApiResponse({ + status: HttpStatus.OK, + type: CourseResponseDto, + description: 'Get all new arrivals course', + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by email', + }) + @Public() + async newArrival( + @Query() query: PaginateQueryDto, + ): Promise { + return this.courseService.newArrival({ + page: query.page, + limit: query.limit, + search: query.search, + }); + } + @Get('most-enroll') + @ApiResponse({ + status: HttpStatus.OK, + type: CourseResponseDto, + description: 'Get all most enroll course', + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by email', + }) + @Public() + async mostEnroll( + @Query() query: PaginateQueryDto, + ): Promise { + return this.courseService.mostEnroll({ + page: query.page, + limit: query.limit, + search: query.search, + }); + } + + + @Get('by-teacher/:teacherId') + @ApiResponse({ + status: HttpStatus.OK, + type: CourseResponseDto, + description: 'Get all course by teacher id', + isArray: true, + }) + @ApiParam({ + name: 'teacherId', + type: String, + description: 'Teacher id', + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by email', + }) + @Roles(Role.TEACHER, Role.ADMIN) + @ApiBearerAuth() + async getAllCourseByTeacherId( + @Param( + 'teacherId', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) teacherId: string, + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + if(request.user.role === Role.TEACHER && request.user.id !== teacherId) { + throw new ForbiddenException('You are not allowed to access this resource'); + } + + return this.courseService.getAllCourseByTeacherId({ + page: query.page, + limit: query.limit, + search: query.search, + teacherId, + }); + } @Get(':id/thumbnail') @Public() @ApiParam({ @@ -139,19 +300,21 @@ export class CourseController { where: { id }, }); if (course.thumbnailKey) - await this.fileService.update(Folder.COURSE_THUMBNAILS, course.thumbnailKey, file); + await this.fileService.update(Folder.COURSE_THUMBNAILS, course.thumbnailKey, file); else { await this.fileService.upload(Folder.COURSE_THUMBNAILS, id, file); } - await this.courseService.update(id, { thumbnailKey: `${id}.${file.originalname.split('.').pop()}` }); + await this.courseService.update(id, { thumbnailKey: `${id}.${file.originalname.split('.').pop()}` }); } - @Get() + + + @Get('with-ownership') @ApiResponse({ status: HttpStatus.OK, type: CourseResponseDto, - description: 'Get all course', + description: 'Get all course with ownership', isArray: true, }) @ApiQuery({ @@ -172,11 +335,12 @@ export class CourseController { required: false, description: 'Search by email', }) - async findAll( + @ApiBearerAuth() + async findAllWithOwnership( @Req() request: AuthenticatedRequest, @Query() query: PaginateQueryDto, ): Promise { - return this.courseService.findAll({ + return this.courseService.findAllWithOwnership({ page: query.page, limit: query.limit, search: query.search, @@ -184,8 +348,7 @@ export class CourseController { role: request.user.role, }); } - - @Get(':id') + @Get('with-ownership/:id') @ApiParam({ name: 'id', type: String, @@ -194,9 +357,10 @@ export class CourseController { @ApiResponse({ status: HttpStatus.OK, type: CourseResponseDto, - description: 'Get course by id', + description: 'Get owned course by id', }) - async findOne( + @ApiBearerAuth() + async findOneWithOwnership( @Req() request: AuthenticatedRequest, @Param( 'id', @@ -215,6 +379,37 @@ export class CourseController { return new CourseResponseDto(course); } + @Get(':id') + @ApiParam({ + name: 'id', + type: String, + description: 'Course id', + }) + @ApiResponse({ + status: HttpStatus.OK, + type: CourseResponseDto, + description: 'Get course by id', + }) + @Public() + async findOne( + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const course = await this.courseService.findOne({ + where: { + id, + status: CourseStatus.PUBLISHED + } + }); + return new CourseResponseDto(course); + } + @Post() @Roles(Role.TEACHER) @ApiResponse({ @@ -222,6 +417,7 @@ export class CourseController { type: CourseResponseDto, description: 'Create course', }) + @ApiBearerAuth() async create( @Req() request: AuthenticatedRequest, @Body() createCourseDto: CreateCourseDto, @@ -253,6 +449,7 @@ export class CourseController { type: CourseResponseDto, description: 'Update course by id', }) + @ApiBearerAuth() async update( @Req() request: AuthenticatedRequest, @Body() updateCourseDto: UpdateCourseDto, @@ -284,6 +481,7 @@ export class CourseController { type: String, description: 'Course id', }) + @ApiBearerAuth() @ApiResponse({ status: HttpStatus.OK, description: 'Delete course by id', @@ -297,7 +495,10 @@ export class CourseController { }), ) id: string, - ): Promise { - await this.courseService.delete(id); + ): Promise { + return await this.courseService.remove(id); } + + + } diff --git a/src/course/course.entity.ts b/src/course/course.entity.ts index a4765d5..792234a 100644 --- a/src/course/course.entity.ts +++ b/src/course/course.entity.ts @@ -36,6 +36,8 @@ export class Course { @ManyToOne(() => User) @JoinColumn({ name: 'teacher_id' }) teacher: User; + @Column({ name: 'teacher_id' }) + teacherId: string; @ManyToOne(() => Category) @JoinColumn({ name: 'category_id' }) diff --git a/src/course/course.service.ts b/src/course/course.service.ts index 45334cf..272cd59 100644 --- a/src/course/course.service.ts +++ b/src/course/course.service.ts @@ -4,8 +4,6 @@ import { Injectable, NotFoundException, } from '@nestjs/common'; -import { CourseStatus, Role } from 'src/shared/enums'; -import { createPagination } from 'src/shared/pagination'; import { FindOneOptions, FindOptionsWhere, ILike, Not, Repository } from 'typeorm'; import { Course } from './course.entity'; import { @@ -13,53 +11,157 @@ import { PaginatedCourseResponeDto, UpdateCourseDto, } from './dtos/index'; +import { CourseStatus, Role } from 'src/shared/enums'; import { EnrollmentStatus } from 'src/enrollment/enums/enrollment-status.enum'; +import { createPagination } from 'src/shared/pagination'; + +interface FindAllParams { + page?: number; + limit?: number; + search?: string; +} + +interface FindAllWithOwnershipParams extends FindAllParams { + userId: string; + role: Role; +} @Injectable() export class CourseService { constructor( @Inject('CourseRepository') private readonly courseRepository: Repository, - ) { } + ) {} + + private readonly defaultRelations = { + teacher: true, + category: true, + }; + + private readonly defaultPagination = { + page: 1, + limit: 20, + }; async findAll({ - page = 1, - limit = 20, + page = this.defaultPagination.page, + limit = this.defaultPagination.limit, search = '', - userId, - role, - }: { - page?: number; - limit?: number; - search?: string; - userId: string; - role: Role; - }): Promise { - const { find } = await createPagination(this.courseRepository, { - page, - limit, + }: FindAllParams): Promise { + const { find } = await this.createPaginatedQuery({ page, limit }); + const whereClause = this.buildSearchClause(search, { + status: CourseStatus.PUBLISHED + }); + + return find({ + where: whereClause, + relations: this.defaultRelations, + }).run(); + } + + async newArrival({ + page = this.defaultPagination.page, + limit = this.defaultPagination.limit, + search = '', + }: FindAllParams): Promise { + const { find } = await this.createPaginatedQuery({ page, limit }); + const whereClause = this.buildSearchClause(search, { + status: CourseStatus.PUBLISHED }); - const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; + return find({ + where: whereClause, + relations: this.defaultRelations, + order: { + createdAt: 'DESC' + } + }).run(); + } + + + async mostEnroll({ + page = this.defaultPagination.page, + limit = this.defaultPagination.limit, + search = '', + }: FindAllParams): Promise { + const skip = (page - 1) * limit; + + const queryBuilder = this.courseRepository.createQueryBuilder('course') + .leftJoinAndSelect('course.teacher', 'teacher') + .leftJoinAndSelect('course.category', 'category') + .leftJoin('course.enrollments', 'enrollment') + .where('course.status = :status', { status: CourseStatus.PUBLISHED }); + + if (search) { + queryBuilder.andWhere('course.title ILIKE :search', { search: `%${search}%` }); + } + + const courses = await queryBuilder + .groupBy('course.id, teacher.id, category.id') + .addSelect('COUNT(enrollment.id)', 'enrollmentCount') + .orderBy('COUNT(enrollment.id)', 'DESC') + .skip(skip) + .take(limit) + .getRawAndEntities(); + + const total = await queryBuilder.getCount(); + + const transformedCourses = courses.entities.map((course, index) => ({ + ...course, + enrollmentCount: parseInt(courses.raw[index].enrollmentCount) + })); + + return new PaginatedCourseResponeDto( + transformedCourses, + total, + limit, + page + ); + } + + + async findAllWithOwnership({ + page = this.defaultPagination.page, + limit = this.defaultPagination.limit, + search = '', + userId, + role, + }: FindAllWithOwnershipParams): Promise { + const { find } = await this.createPaginatedQuery({ page, limit }); + const baseSearch = this.buildSearchClause(search); const whereCondition = this.buildWhereCondition(userId, role, baseSearch); - const courses = await find({ + return find({ where: whereCondition, - relations: { - teacher: true, - category: true, - }, + relations: this.defaultRelations, }).run(); + } - return courses; + async getAllCourseByTeacherId({ + page = this.defaultPagination.page, + limit = this.defaultPagination.limit, + search = '', + teacherId, + }: FindAllParams & { teacherId: string }): Promise { + const { find } = await this.createPaginatedQuery({ page, limit }); + const whereClause = this.buildSearchClause(search, { + teacher: { id: teacherId } + }); + + return find({ + where: whereClause, + relations: this.defaultRelations, + }).run(); } async findOne(options: FindOneOptions): Promise { - const course = await this.courseRepository.findOne(options); - if (!course) throw new NotFoundException('Course not found'); - return course; - } + const course = await this.courseRepository.findOne({ + ...options, + relations: this.defaultRelations, + }); + return this.ensureCourseExists(course); + } async findOneWithOwnership( userId: string, @@ -71,17 +173,10 @@ export class CourseService { const course = await this.courseRepository.findOne({ where: whereCondition, - relations: { - teacher: true, - category: true, - }, + relations: this.defaultRelations, }); - if (!course) { - throw new NotFoundException('Course not found'); - } - - return course; + return this.ensureCourseExists(course); } async create( @@ -95,26 +190,18 @@ export class CourseService { category: { id: createCourseDto.categoryId }, }); - return this.courseRepository.save(course); + return await this.courseRepository.save(course); } catch (error) { if (error instanceof Error) { throw new BadRequestException(error.message); } + throw error; } } - async update(id: string, updateCourseDto: UpdateCourseDto): Promise { - const existingCourse = await this.courseRepository.findOne({ - where: { id }, - relations: { - teacher: true, - category: true, - }, - }); - - if (!existingCourse) { - throw new NotFoundException('Course not found'); - } + async update(id: string, updateCourseDto: UpdateCourseDto): Promise { + const existingCourse = await this.findOne({ where: { id } }); + this.validateStatusTransition( existingCourse.status, updateCourseDto.status, @@ -129,20 +216,40 @@ export class CourseService { return updatedCourse; } - async delete(id: string): Promise { - try { - await this.courseRepository.delete(id); - } catch (error) { - throw new NotFoundException('Course not found'); + async remove(id: string): Promise { + const course = await this.findOne({ where: { id } }); + return await this.courseRepository.remove(course); + } + + async validateOwnership(id: string, userId: string): Promise { + const course = await this.courseRepository.findOne({ + where: { id }, + relations: { teacher: true }, + }); + this.ensureCourseExists(course); + + if (course.teacher.id !== userId) { + throw new BadRequestException('You can only access your own courses'); } } + private async createPaginatedQuery({ page, limit }: { page: number; limit: number }) { + return await createPagination(this.courseRepository, { page, limit }); + } + + private buildSearchClause(search: string, additionalCriteria = {}): FindOptionsWhere { + return { + ...additionalCriteria, + ...(search ? { title: ILike(`%${search}%`) } : {}), + }; + } + private buildWhereCondition( userId: string, role: Role, baseCondition: FindOptionsWhere = {}, ): FindOptionsWhere | FindOptionsWhere[] { - const conditions: Record< + const roleConditions: Record< Role, () => FindOptionsWhere | FindOptionsWhere[] > = { @@ -167,8 +274,7 @@ export class CourseService { [Role.ADMIN]: () => baseCondition, }; - const buildCondition = conditions[role]; - + const buildCondition = roleConditions[role]; if (!buildCondition) { throw new BadRequestException('Invalid role'); } @@ -191,13 +297,11 @@ export class CourseService { ); } } - async validateOwnership(id: string, userId: string): Promise { - const course = await this.courseRepository.findOne({ - where: { id }, - relations: { teacher: true }, - }); - if (!course) throw new NotFoundException('Course not found'); - if (course.teacher.id !== userId) - throw new BadRequestException('You can only access your own courses'); + + private ensureCourseExists(course: Course | null): Course { + if (!course) { + throw new NotFoundException('Course not found'); + } + return course; } -} +} \ No newline at end of file diff --git a/src/course/dtos/create-course.dto.ts b/src/course/dtos/create-course.dto.ts index 3d43a4f..7bab581 100644 --- a/src/course/dtos/create-course.dto.ts +++ b/src/course/dtos/create-course.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsEnum, IsNotEmpty } from 'class-validator'; +import { IsEnum, IsNotEmpty, IsUUID } from 'class-validator'; import { CourseLevel, CourseStatus } from 'src/shared/enums/index'; export class CreateCourseDto { @IsNotEmpty() @@ -18,6 +18,7 @@ export class CreateCourseDto { }) description: string; + @IsUUID(4) @IsNotEmpty() @ApiProperty({ description: 'Category Id', From a298019efbb1c0cc05468534ec796c39fdcd3854 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Mon, 25 Nov 2024 19:30:37 +0700 Subject: [PATCH 110/155] feat: add return type in void --- src/exam-answer/exam-answer.controller.ts | 5 +++-- src/exam-answer/exam-answer.service.ts | 11 +++++----- src/exam-attempt/exam-attempt.controller.ts | 6 +++-- src/exam-attempt/exam-attempt.service.dto.ts | 3 ++- src/exam/exam.controller.ts | 22 ++++++++++++++----- src/exam/exam.service.ts | 16 +++++++++++--- .../question-option.controller.ts | 15 ++++++++----- .../question-option.service.ts | 6 +++-- src/question/question.controller.ts | 11 ++++++---- src/question/question.service.ts | 10 +++++++-- src/shared/enums/question-type.enum.ts | 1 + 11 files changed, 74 insertions(+), 32 deletions(-) diff --git a/src/exam-answer/exam-answer.controller.ts b/src/exam-answer/exam-answer.controller.ts index fcae667..8a7aee7 100644 --- a/src/exam-answer/exam-answer.controller.ts +++ b/src/exam-answer/exam-answer.controller.ts @@ -259,11 +259,12 @@ export class ExamAnswerController { }), ) id: string, - ): Promise { - await this.examAnswerService.deleteExamAnswer( + ): Promise { + const examAnswer = await this.examAnswerService.deleteExamAnswer( request.user.id, request.user.role, id, ); + return new ExamAnswerResponseDto(examAnswer); } } diff --git a/src/exam-answer/exam-answer.service.ts b/src/exam-answer/exam-answer.service.ts index a530da8..29a7c16 100644 --- a/src/exam-answer/exam-answer.service.ts +++ b/src/exam-answer/exam-answer.service.ts @@ -11,13 +11,14 @@ import { FindOptionsSelect, FindOptionsWhere, ILike, + Not, Repository, } from 'typeorm'; import { ExamAnswer } from './exam-answer.entity'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { PaginatedExamAnswerResponseDto } from './dtos/exam-answer-response.dto'; import { createPagination } from 'src/shared/pagination'; -import { ExamStatus, Role } from 'src/shared/enums'; +import { ExamStatus, QuestionType, Role } from 'src/shared/enums'; import { CreateExamAnswerDto } from './dtos/create-exam-answer.dto'; import { Question } from 'src/question/question.entity'; import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; @@ -339,11 +340,11 @@ export class ExamAnswerService { userId: string, role: Role, id: string, - ): Promise { + ): Promise { try { - if (await this.findOne(userId, role, { where: { id } })) { - await this.examAnswerRepository.delete(id); - } + const examAnswer = await this.findOne(userId, role, { where: { id } }); + await this.examAnswerRepository.delete(id); + return examAnswer; } catch (error) { if (error instanceof Error) throw new NotFoundException('Exam answer not found'); diff --git a/src/exam-attempt/exam-attempt.controller.ts b/src/exam-attempt/exam-attempt.controller.ts index d5f8a01..d670d45 100644 --- a/src/exam-attempt/exam-attempt.controller.ts +++ b/src/exam-attempt/exam-attempt.controller.ts @@ -25,6 +25,7 @@ import { Roles } from 'src/shared/decorators/role.decorator'; import { Role } from 'src/shared/enums'; import { CreateExamAttemptDto } from './dtos/create-exam-attempt.dto'; import { UpdateExamAttemptDto } from './dtos/update-exam-attempt.dto'; +import { Exam } from 'src/exam/exam.entity'; @Controller('exam-attempt') @ApiTags('ExamAttempt') @@ -191,11 +192,12 @@ export class ExamAttemptController { }), ) id: string, - ): Promise { - await this.examAttemptService.deleteExamAttempt( + ): Promise { + const examAttempt = await this.examAttemptService.deleteExamAttempt( request.user.id, request.user.role, id, ); + return new ExamAttemptResponseDto(examAttempt); } } diff --git a/src/exam-attempt/exam-attempt.service.dto.ts b/src/exam-attempt/exam-attempt.service.dto.ts index 6f7d7d3..10993d6 100644 --- a/src/exam-attempt/exam-attempt.service.dto.ts +++ b/src/exam-attempt/exam-attempt.service.dto.ts @@ -217,10 +217,11 @@ export class ExamAttemptService { userId: string, role: Role, id: string, - ): Promise { + ): Promise { try { const examAttempt = await this.findOne(userId, role, { where: { id } }); await this.examAttemptRepository.delete(id); + return examAttempt; } catch (error) { if (error instanceof Error) throw new NotFoundException('Exam-attempt not found'); diff --git a/src/exam/exam.controller.ts b/src/exam/exam.controller.ts index 83a2001..f10df89 100644 --- a/src/exam/exam.controller.ts +++ b/src/exam/exam.controller.ts @@ -31,7 +31,10 @@ import { CreateExamDto } from './dtos/create-exam.dto'; import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { UpdateExamDto } from './dtos/update-exam.dto'; -import { PaginatedQuestionResponseDto } from 'src/question/dtos/question-response.dto'; +import { + PaginatedQuestionResponseDto, + QuestionResponseDto, +} from 'src/question/dtos/question-response.dto'; import { Public } from 'src/shared/decorators/public.decorator'; @Controller('exam') @@ -163,8 +166,13 @@ export class ExamController { }), ) id: string, - ): Promise { - await this.examService.deleteExam(request.user.id, request.user.role, id); + ): Promise { + const exam = await this.examService.deleteExam( + request.user.id, + request.user.role, + id, + ); + return new ExamResponseDto(exam); } @Post('generate/:examId') @@ -183,7 +191,11 @@ export class ExamController { }), ) examId: string, - ): Promise { - return this.examService.createQuestionAndChoice(examId, request.user.id); + ): Promise { + const questions = await this.examService.createQuestionAndChoice( + examId, + request.user.id, + ); + return questions.map((question) => new QuestionResponseDto(question)); } } diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts index b484fab..4931737 100644 --- a/src/exam/exam.service.ts +++ b/src/exam/exam.service.ts @@ -26,6 +26,7 @@ import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request. import { UserService } from 'src/user/user.service'; import { EnrollmentService } from 'src/enrollment/enrollment.service'; import { PretestDto } from './dtos/pretest.dto'; +import { Question } from 'src/question/question.entity'; @Injectable() export class ExamService { @@ -205,12 +206,13 @@ export class ExamService { }); } - async deleteExam(userId: string, role: Role, id: string): Promise { + async deleteExam(userId: string, role: Role, id: string): Promise { try { const exam = await this.findOne(userId, role, { where: { id } }); if (this.checkPermission(userId, role, exam) === false) throw new ForbiddenException('Can not change this exam'); await this.examRepository.delete(id); + return exam; } catch (error) { if (error instanceof Error) throw new NotFoundException('Exam not found'); } @@ -292,7 +294,11 @@ export class ExamService { } } - async createQuestionAndChoice(examId: string, userId: string): Promise { + async createQuestionAndChoice( + examId: string, + userId: string, + ): Promise { + let questions = []; const fetchData = await this.fetchData(examId, userId); let orderIndex = (await this.questionService.getMaxOrderIndex(examId)) + 1; await Promise.all( @@ -300,7 +306,7 @@ export class ExamService { const createQuestionDto = { examId, question: data.question, - type: QuestionType.MULTIPLE_CHOICE, + type: QuestionType.PRETEST, points: 1, orderIndex: orderIndex++, }; @@ -309,6 +315,8 @@ export class ExamService { createQuestionDto, ); + questions.push(question); + await Promise.all( Object.entries(data.choices).map(([key, value]) => { const createQuestionOptionDto = { @@ -325,5 +333,7 @@ export class ExamService { ); }), ); + + return questions; } } diff --git a/src/question-option/question-option.controller.ts b/src/question-option/question-option.controller.ts index 293d2f2..fea7f99 100644 --- a/src/question-option/question-option.controller.ts +++ b/src/question-option/question-option.controller.ts @@ -214,11 +214,14 @@ export class QuestionOptionController { }), ) id: string, - ): Promise { - await this.questionOptionService.deleteQuestionOption( - request.user.id, - request.user.role, - id, - ); + ): Promise { + const questionOption = + await this.questionOptionService.deleteQuestionOption( + request.user.id, + request.user.role, + id, + ); + + return new QuestionOptionResponseDto(questionOption); } } diff --git a/src/question-option/question-option.service.ts b/src/question-option/question-option.service.ts index 4f038c9..0a7539a 100644 --- a/src/question-option/question-option.service.ts +++ b/src/question-option/question-option.service.ts @@ -10,13 +10,14 @@ import { FindOptionsSelect, FindOptionsWhere, ILike, + Not, Repository, } from 'typeorm'; import { QuestionOption } from './question-option.entity'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { PaginatedQuestionOptionResponseDto } from './dtos/question-option-response.dto'; import { createPagination } from 'src/shared/pagination'; -import { ExamStatus, Role } from 'src/shared/enums'; +import { ExamStatus, QuestionType, Role } from 'src/shared/enums'; import { CreateQuestionOptionDto } from './dtos/create-question-option.dto'; import { Question } from 'src/question/question.entity'; import { UpdateQuestionOptionDto } from './dtos/update-question-option.dto'; @@ -270,7 +271,7 @@ export class QuestionOptionService { userId: string, role: Role, id: string, - ): Promise { + ): Promise { try { const questionOption = await this.findOne(userId, role, { where: { id }, @@ -278,6 +279,7 @@ export class QuestionOptionService { if (this.checkPermission(userId, role, questionOption) === false) throw new ForbiddenException('Can not change this question option'); await this.questionOptionRepository.delete(id); + return questionOption; } catch (error) { if (error instanceof Error) throw new NotFoundException('Question option not found'); diff --git a/src/question/question.controller.ts b/src/question/question.controller.ts index 7e50204..a4a5db9 100644 --- a/src/question/question.controller.ts +++ b/src/question/question.controller.ts @@ -160,8 +160,9 @@ export class QuestionController { async createQuestion( @Body() createQuestionDto: CreateQuestionDto, ): Promise { - const question = - await this.questionService.createQuestion(createQuestionDto); + const question = await this.questionService.createQuestion( + createQuestionDto, + ); return new QuestionResponseDto(question); } @@ -211,11 +212,13 @@ export class QuestionController { }), ) id: string, - ): Promise { - await this.questionService.deleteQuestion( + ): Promise { + const question = await this.questionService.deleteQuestion( request.user.id, request.user.role, id, ); + + return new QuestionResponseDto(question); } } diff --git a/src/question/question.service.ts b/src/question/question.service.ts index 00a8424..efad6ef 100644 --- a/src/question/question.service.ts +++ b/src/question/question.service.ts @@ -11,12 +11,13 @@ import { FindOptionsSelect, FindOptionsWhere, ILike, + Not, Repository, } from 'typeorm'; import { PaginatedQuestionResponseDto } from './dtos/question-response.dto'; import { createPagination } from 'src/shared/pagination'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; -import { ExamStatus, Role } from 'src/shared/enums'; +import { ExamStatus, QuestionType, Role } from 'src/shared/enums'; import { CreateQuestionDto } from './dtos/create-question.dto'; import { UpdateQuestionDto } from './dtos/update-question.dto'; import { Exam } from 'src/exam/exam.entity'; @@ -328,13 +329,18 @@ export class QuestionService { } } - async deleteQuestion(userId: string, role: Role, id: string): Promise { + async deleteQuestion( + userId: string, + role: Role, + id: string, + ): Promise { try { const question = await this.findOne(userId, role, { where: { id } }); if (this.checkPermission(userId, role, question) === false) throw new BadRequestException('Can not change this question'); await this.questionRepository.delete(id); await this.reOrderIndex(question.examId); + return question; } catch (error) { if (error instanceof Error) throw new NotFoundException('Question not found'); diff --git a/src/shared/enums/question-type.enum.ts b/src/shared/enums/question-type.enum.ts index f657d20..b5ce356 100644 --- a/src/shared/enums/question-type.enum.ts +++ b/src/shared/enums/question-type.enum.ts @@ -2,4 +2,5 @@ export enum QuestionType { MULTIPLE_CHOICE = 'multiple_choice', TRUE_FALSE = 'true_false', ESSAY = 'essay', + PRETEST = 'pretest', } From 910059ae7190598080e2a708712702a43697f525 Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Mon, 25 Nov 2024 20:02:19 +0700 Subject: [PATCH 111/155] refactor(course): enhance query structure and fix enrollment count retrieval in course service --- src/course/course.service.ts | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/course/course.service.ts b/src/course/course.service.ts index 272cd59..52edc4d 100644 --- a/src/course/course.service.ts +++ b/src/course/course.service.ts @@ -85,30 +85,33 @@ export class CourseService { search = '', }: FindAllParams): Promise { const skip = (page - 1) * limit; - - const queryBuilder = this.courseRepository.createQueryBuilder('course') + + const queryBuilder = this.courseRepository + .createQueryBuilder('course') .leftJoinAndSelect('course.teacher', 'teacher') .leftJoinAndSelect('course.category', 'category') - .leftJoin('course.enrollments', 'enrollment') + .leftJoin('course.enrollments', 'enrollments') .where('course.status = :status', { status: CourseStatus.PUBLISHED }); - + if (search) { queryBuilder.andWhere('course.title ILIKE :search', { search: `%${search}%` }); } - + + const total = await queryBuilder.getCount(); + const courses = await queryBuilder - .groupBy('course.id, teacher.id, category.id') - .addSelect('COUNT(enrollment.id)', 'enrollmentCount') - .orderBy('COUNT(enrollment.id)', 'DESC') - .skip(skip) - .take(limit) + .addSelect('COUNT(enrollments.id)', 'enrollmentCount') + .groupBy('course.id') + .addGroupBy('teacher.id') + .addGroupBy('category.id') + .orderBy('COUNT(enrollments.id)', 'DESC') + .offset(skip) + .limit(limit) .getRawAndEntities(); - - const total = await queryBuilder.getCount(); - + const transformedCourses = courses.entities.map((course, index) => ({ ...course, - enrollmentCount: parseInt(courses.raw[index].enrollmentCount) + enrollmentCount: parseInt(courses.raw[index].enrollment_count) || 0 })); return new PaginatedCourseResponeDto( From 66edba77efb1ecb6307e472a20d624c9fa3ad92f Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Mon, 25 Nov 2024 20:56:02 +0700 Subject: [PATCH 112/155] feat: check with enroll --- src/exam-answer/exam-answer.service.ts | 32 +++++++++++++- src/exam-attempt/exam-attempt.service.dto.ts | 27 +++++++++++- src/exam/exam.module.ts | 2 + src/exam/exam.service.ts | 44 +++++++++++++++---- .../question-option.service.ts | 25 +++++++++-- src/question/question.service.ts | 25 +++++++++-- 6 files changed, 136 insertions(+), 19 deletions(-) diff --git a/src/exam-answer/exam-answer.service.ts b/src/exam-answer/exam-answer.service.ts index 29a7c16..8fbca61 100644 --- a/src/exam-answer/exam-answer.service.ts +++ b/src/exam-answer/exam-answer.service.ts @@ -18,7 +18,7 @@ import { ExamAnswer } from './exam-answer.entity'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { PaginatedExamAnswerResponseDto } from './dtos/exam-answer-response.dto'; import { createPagination } from 'src/shared/pagination'; -import { ExamStatus, QuestionType, Role } from 'src/shared/enums'; +import { CourseStatus, ExamStatus, QuestionType, Role } from 'src/shared/enums'; import { CreateExamAnswerDto } from './dtos/create-exam-answer.dto'; import { Question } from 'src/question/question.entity'; import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; @@ -88,6 +88,21 @@ export class ExamAnswerService { examAttempt: { userId, }, + question: { + exam: { + status: ExamStatus.PUBLISHED, + courseModule: { + course: { + status: CourseStatus.PUBLISHED, + enrollments: { + user: { + id: userId, + }, + }, + }, + }, + }, + }, }, ]; } @@ -121,6 +136,21 @@ export class ExamAnswerService { examAttempt: { userId, }, + question: { + exam: { + status: ExamStatus.PUBLISHED, + courseModule: { + course: { + status: CourseStatus.PUBLISHED, + enrollments: { + user: { + id: userId, + }, + }, + }, + }, + }, + }, }, ]; } diff --git a/src/exam-attempt/exam-attempt.service.dto.ts b/src/exam-attempt/exam-attempt.service.dto.ts index 10993d6..4d8270c 100644 --- a/src/exam-attempt/exam-attempt.service.dto.ts +++ b/src/exam-attempt/exam-attempt.service.dto.ts @@ -17,7 +17,12 @@ import { import { ExamAttempt } from './exam-attempt.entity'; import { PaginatedExamAttemptResponseDto } from './dtos/exam-attempt-response.dto'; import { createPagination } from 'src/shared/pagination'; -import { ExamAttemptStatus, ExamStatus, Role } from 'src/shared/enums'; +import { + CourseStatus, + ExamAttemptStatus, + ExamStatus, + Role, +} from 'src/shared/enums'; import { CreateExamAttemptDto } from './dtos/create-exam-attempt.dto'; import { Exam } from 'src/exam/exam.entity'; import { UpdateExamAttemptDto } from './dtos/update-exam-attempt.dto'; @@ -86,6 +91,16 @@ export class ExamAttemptService { userId: userId, exam: { status: ExamStatus.PUBLISHED, + courseModule: { + course: { + status: CourseStatus.PUBLISHED, + enrollments: { + user: { + id: userId, + }, + }, + }, + }, }, }; } @@ -110,6 +125,16 @@ export class ExamAttemptService { userId: userId, exam: { status: ExamStatus.PUBLISHED, + courseModule: { + course: { + status: CourseStatus.PUBLISHED, + enrollments: { + user: { + id: userId, + }, + }, + }, + }, }, }; } diff --git a/src/exam/exam.module.ts b/src/exam/exam.module.ts index df23a58..73d7cb7 100644 --- a/src/exam/exam.module.ts +++ b/src/exam/exam.module.ts @@ -12,6 +12,7 @@ import { ExamAnswerModule } from 'src/exam-answer/exam-answer.module'; import { HttpModule } from '@nestjs/axios'; import { UserModule } from 'src/user/user.module'; import { EnrollmentModule } from 'src/enrollment/enrollment.module'; +import { ConfigModule } from '@nestjs/config'; @Module({ imports: [ @@ -22,6 +23,7 @@ import { EnrollmentModule } from 'src/enrollment/enrollment.module'; HttpModule, UserModule, EnrollmentModule, + ConfigModule, ], controllers: [ExamController], providers: [...examProviders, ExamService], diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts index 4931737..f8def56 100644 --- a/src/exam/exam.service.ts +++ b/src/exam/exam.service.ts @@ -16,7 +16,7 @@ import { Exam } from './exam.entity'; import { CreateExamDto } from './dtos/create-exam.dto'; import { PaginatedExamResponseDto } from './dtos/exam-response.dto'; import { createPagination } from 'src/shared/pagination'; -import { ExamStatus, QuestionType, Role } from 'src/shared/enums'; +import { CourseStatus, ExamStatus, QuestionType, Role } from 'src/shared/enums'; import { UpdateExamDto } from './dtos/update-exam.dto'; import { CourseModule } from 'src/course-module/course-module.entity'; import { QuestionService } from 'src/question/question.service'; @@ -27,6 +27,8 @@ import { UserService } from 'src/user/user.service'; import { EnrollmentService } from 'src/enrollment/enrollment.service'; import { PretestDto } from './dtos/pretest.dto'; import { Question } from 'src/question/question.entity'; +import { ConfigService } from '@nestjs/config'; +import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; @Injectable() export class ExamService { @@ -40,6 +42,7 @@ export class ExamService { private readonly httpService: HttpService, private readonly userService: UserService, private readonly enrollService: EnrollmentService, + private readonly configService: ConfigService, ) {} async findAll( @@ -93,7 +96,20 @@ export class ExamService { const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; if (role === Role.STUDENT) { - return { ...baseSearch, status: ExamStatus.PUBLISHED }; + return { + ...baseSearch, + status: ExamStatus.PUBLISHED, + courseModule: { + course: { + status: CourseStatus.PUBLISHED, + enrollments: { + user: { + id: userId, + }, + }, + }, + }, + }; } if (role === Role.TEACHER) { @@ -108,10 +124,6 @@ export class ExamService { }, }, }, - { - ...baseSearch, - status: ExamStatus.PUBLISHED, - }, ]; } @@ -119,7 +131,20 @@ export class ExamService { return { ...baseSearch }; } - return { ...baseSearch, status: ExamStatus.PUBLISHED }; + return { + ...baseSearch, + status: ExamStatus.PUBLISHED, + courseModule: { + course: { + status: CourseStatus.PUBLISHED, + enrollments: { + user: { + id: userId, + }, + }, + }, + }, + }; } async findOne( @@ -245,7 +270,6 @@ export class ExamService { } async fetchData(examId: string, userId: string): Promise { - const api = 'https://ai.edusaig.com/ai'; const user = await this.userService.findOne({ where: { id: userId } }); if (!user) throw new NotFoundException('Not Found User'); const exam = await this.examRepository.findOne({ where: { id: examId } }); @@ -285,7 +309,9 @@ export class ExamService { }; const response = await this.httpService.axiosRef.post( - `${api}/generate-pretest/`, + `${this.configService.get( + GLOBAL_CONFIG.AI_URL, + )}/generate-pretest/`, requestBody, ); return { data: response.data }; diff --git a/src/question-option/question-option.service.ts b/src/question-option/question-option.service.ts index 0a7539a..6014a7c 100644 --- a/src/question-option/question-option.service.ts +++ b/src/question-option/question-option.service.ts @@ -17,7 +17,7 @@ import { QuestionOption } from './question-option.entity'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { PaginatedQuestionOptionResponseDto } from './dtos/question-option-response.dto'; import { createPagination } from 'src/shared/pagination'; -import { ExamStatus, QuestionType, Role } from 'src/shared/enums'; +import { CourseStatus, ExamStatus, QuestionType, Role } from 'src/shared/enums'; import { CreateQuestionOptionDto } from './dtos/create-question-option.dto'; import { Question } from 'src/question/question.entity'; import { UpdateQuestionOptionDto } from './dtos/update-question-option.dto'; @@ -169,6 +169,16 @@ export class QuestionOptionService { question: { exam: { status: ExamStatus.PUBLISHED, + courseModule: { + course: { + status: CourseStatus.PUBLISHED, + enrollments: { + user: { + id: userId, + }, + }, + }, + }, }, }, }; @@ -179,9 +189,6 @@ export class QuestionOptionService { ...baseSearch, question: { exam: [ - { - status: ExamStatus.PUBLISHED, - }, { courseModule: { course: { @@ -203,6 +210,16 @@ export class QuestionOptionService { question: { exam: { status: ExamStatus.PUBLISHED, + courseModule: { + course: { + status: CourseStatus.PUBLISHED, + enrollments: { + user: { + id: userId, + }, + }, + }, + }, }, }, }; diff --git a/src/question/question.service.ts b/src/question/question.service.ts index efad6ef..2ea8a5d 100644 --- a/src/question/question.service.ts +++ b/src/question/question.service.ts @@ -17,7 +17,7 @@ import { import { PaginatedQuestionResponseDto } from './dtos/question-response.dto'; import { createPagination } from 'src/shared/pagination'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; -import { ExamStatus, QuestionType, Role } from 'src/shared/enums'; +import { CourseStatus, ExamStatus, QuestionType, Role } from 'src/shared/enums'; import { CreateQuestionDto } from './dtos/create-question.dto'; import { UpdateQuestionDto } from './dtos/update-question.dto'; import { Exam } from 'src/exam/exam.entity'; @@ -194,6 +194,16 @@ export class QuestionService { ...baseSearch, exam: { status: ExamStatus.PUBLISHED, + courseModule: { + course: { + status: CourseStatus.PUBLISHED, + enrollments: { + user: { + id: userId, + }, + }, + }, + }, }, }; } @@ -202,9 +212,6 @@ export class QuestionService { return { ...baseSearch, exam: [ - { - status: ExamStatus.PUBLISHED, - }, { courseModule: { course: { @@ -224,6 +231,16 @@ export class QuestionService { ...baseSearch, exam: { status: ExamStatus.PUBLISHED, + courseModule: { + course: { + status: CourseStatus.PUBLISHED, + enrollments: { + user: { + id: userId, + }, + }, + }, + }, }, }; } From 3ad1ab98970abd1f4a5a21f842284656464fe1bb Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Mon, 25 Nov 2024 22:25:17 +0700 Subject: [PATCH 113/155] feat: update return in delete --- .../dtos/create-exam-answer.dto.ts | 4 +-- src/exam-answer/exam-answer.controller.ts | 4 +-- src/exam-answer/exam-answer.entity.ts | 2 +- src/exam-answer/exam-answer.service.ts | 3 +- .../dtos/create-exam-attempt.dto.ts | 4 +-- src/exam-attempt/exam-attempt.controller.ts | 4 +-- src/exam-attempt/exam-attempt.entity.ts | 2 +- src/exam-attempt/exam-attempt.service.dto.ts | 3 +- src/exam/dtos/create-exam.dto.ts | 8 ++--- src/exam/exam.controller.ts | 4 +-- src/exam/exam.entity.ts | 5 +-- src/exam/exam.service.ts | 33 +++++++++++-------- .../question-option.controller.ts | 4 +-- .../question-option.service.ts | 3 +- src/question/question.controller.ts | 4 +-- src/question/question.service.ts | 4 +-- src/shared/enums/question-type.enum.ts | 1 - 17 files changed, 48 insertions(+), 44 deletions(-) diff --git a/src/exam-answer/dtos/create-exam-answer.dto.ts b/src/exam-answer/dtos/create-exam-answer.dto.ts index c42a277..d484c51 100644 --- a/src/exam-answer/dtos/create-exam-answer.dto.ts +++ b/src/exam-answer/dtos/create-exam-answer.dto.ts @@ -35,12 +35,12 @@ export class CreateExamAnswerDto { }) isCorrect: boolean; - @IsNotEmpty() + @IsOptional() @IsInt() @ApiProperty({ description: 'Points in this answer', type: Number, example: 0, }) - points: number = 0; + points?: number; } diff --git a/src/exam-answer/exam-answer.controller.ts b/src/exam-answer/exam-answer.controller.ts index 8a7aee7..bd2afe6 100644 --- a/src/exam-answer/exam-answer.controller.ts +++ b/src/exam-answer/exam-answer.controller.ts @@ -245,10 +245,10 @@ export class ExamAnswerController { @Delete(':id') @ApiResponse({ - status: HttpStatus.NO_CONTENT, + status: HttpStatus.OK, description: 'Delete an exam', + type: ExamAnswerResponseDto, }) - @HttpCode(HttpStatus.NO_CONTENT) async deleteExam( @Req() request: AuthenticatedRequest, @Param( diff --git a/src/exam-answer/exam-answer.entity.ts b/src/exam-answer/exam-answer.entity.ts index eed19ef..75366c3 100644 --- a/src/exam-answer/exam-answer.entity.ts +++ b/src/exam-answer/exam-answer.entity.ts @@ -67,7 +67,7 @@ export class ExamAnswer { nullable: false, default: 0, }) - points: number; + points: number = 0; @CreateDateColumn({ type: 'timestamp with time zone', diff --git a/src/exam-answer/exam-answer.service.ts b/src/exam-answer/exam-answer.service.ts index 8fbca61..566a854 100644 --- a/src/exam-answer/exam-answer.service.ts +++ b/src/exam-answer/exam-answer.service.ts @@ -373,8 +373,7 @@ export class ExamAnswerService { ): Promise { try { const examAnswer = await this.findOne(userId, role, { where: { id } }); - await this.examAnswerRepository.delete(id); - return examAnswer; + return await this.examAnswerRepository.remove(examAnswer); } catch (error) { if (error instanceof Error) throw new NotFoundException('Exam answer not found'); diff --git a/src/exam-attempt/dtos/create-exam-attempt.dto.ts b/src/exam-attempt/dtos/create-exam-attempt.dto.ts index a8100bc..b1987ff 100644 --- a/src/exam-attempt/dtos/create-exam-attempt.dto.ts +++ b/src/exam-attempt/dtos/create-exam-attempt.dto.ts @@ -11,7 +11,7 @@ export class CreateExamAttemptDto { }) examId: string; - @IsNotEmpty() + @IsOptional() @Min(0) @IsInt() @ApiProperty({ @@ -19,7 +19,7 @@ export class CreateExamAttemptDto { type: Number, example: 0, }) - score: number = 0; + score?: number; @IsNotEmpty() @IsEnum(ExamAttemptStatus) diff --git a/src/exam-attempt/exam-attempt.controller.ts b/src/exam-attempt/exam-attempt.controller.ts index d670d45..55e2c2f 100644 --- a/src/exam-attempt/exam-attempt.controller.ts +++ b/src/exam-attempt/exam-attempt.controller.ts @@ -178,10 +178,10 @@ export class ExamAttemptController { @Roles(Role.TEACHER) @Roles(Role.ADMIN) @ApiResponse({ - status: HttpStatus.NO_CONTENT, + status: HttpStatus.OK, description: 'Delete an exam', + type: ExamAttemptResponseDto, }) - @HttpCode(HttpStatus.NO_CONTENT) async deleteExamAttempt( @Req() request: AuthenticatedRequest, @Param( diff --git a/src/exam-attempt/exam-attempt.entity.ts b/src/exam-attempt/exam-attempt.entity.ts index b5b45b6..440723a 100644 --- a/src/exam-attempt/exam-attempt.entity.ts +++ b/src/exam-attempt/exam-attempt.entity.ts @@ -49,7 +49,7 @@ export class ExamAttempt { default: 0, type: 'decimal', }) - score: number; + score: number = 0; @Column({ enum: ExamAttemptStatus, diff --git a/src/exam-attempt/exam-attempt.service.dto.ts b/src/exam-attempt/exam-attempt.service.dto.ts index 4d8270c..f3c2003 100644 --- a/src/exam-attempt/exam-attempt.service.dto.ts +++ b/src/exam-attempt/exam-attempt.service.dto.ts @@ -245,8 +245,7 @@ export class ExamAttemptService { ): Promise { try { const examAttempt = await this.findOne(userId, role, { where: { id } }); - await this.examAttemptRepository.delete(id); - return examAttempt; + return await this.examAttemptRepository.remove(examAttempt); } catch (error) { if (error instanceof Error) throw new NotFoundException('Exam-attempt not found'); diff --git a/src/exam/dtos/create-exam.dto.ts b/src/exam/dtos/create-exam.dto.ts index 7169f5a..47919e0 100644 --- a/src/exam/dtos/create-exam.dto.ts +++ b/src/exam/dtos/create-exam.dto.ts @@ -59,16 +59,16 @@ export class CreateExamDto { }) maxAttempts: number; - @IsNotEmpty() + @IsOptional() @IsBoolean() @ApiProperty({ description: 'Shuffle question.', type: Boolean, example: false, }) - shuffleQuestions: boolean = false; + shuffleQuestions?: boolean; - @IsNotEmpty() + @IsOptional() @IsEnum(ExamStatus) @ApiProperty({ description: 'Exam status', @@ -76,5 +76,5 @@ export class CreateExamDto { example: ExamStatus.DRAFT, enum: ExamStatus, }) - status: ExamStatus = ExamStatus.DRAFT; + status?: ExamStatus; } diff --git a/src/exam/exam.controller.ts b/src/exam/exam.controller.ts index f10df89..10d357f 100644 --- a/src/exam/exam.controller.ts +++ b/src/exam/exam.controller.ts @@ -152,10 +152,10 @@ export class ExamController { @Roles(Role.TEACHER) @Roles(Role.ADMIN) @ApiResponse({ - status: HttpStatus.NO_CONTENT, + status: HttpStatus.OK, description: 'Delete an exam', + type: ExamResponseDto, }) - @HttpCode(HttpStatus.NO_CONTENT) async deleteExam( @Req() request: AuthenticatedRequest, @Param( diff --git a/src/exam/exam.entity.ts b/src/exam/exam.entity.ts index daa3d72..6bc2aaf 100644 --- a/src/exam/exam.entity.ts +++ b/src/exam/exam.entity.ts @@ -68,14 +68,15 @@ export class Exam { nullable: false, default: false, }) - shuffleQuestions: boolean; + shuffleQuestions: boolean = false; @Column({ + type: 'enum', enum: ExamStatus, nullable: false, default: ExamStatus.DRAFT, }) - status: ExamStatus; + status: ExamStatus = ExamStatus.DRAFT; @CreateDateColumn({ type: 'timestamp with time zone', diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts index f8def56..8128ed9 100644 --- a/src/exam/exam.service.ts +++ b/src/exam/exam.service.ts @@ -213,6 +213,8 @@ export class ExamService { examInData.status != ExamStatus.DRAFT && updateExamDto.status == ExamStatus.DRAFT ) { + console.log(examInData.status); + console.log(updateExamDto.status); throw new ForbiddenException("Can't change status to draft"); } @@ -236,8 +238,7 @@ export class ExamService { const exam = await this.findOne(userId, role, { where: { id } }); if (this.checkPermission(userId, role, exam) === false) throw new ForbiddenException('Can not change this exam'); - await this.examRepository.delete(id); - return exam; + return await this.examRepository.remove(exam); } catch (error) { if (error instanceof Error) throw new NotFoundException('Exam not found'); } @@ -296,18 +297,24 @@ export class ExamService { }, createdAt: new Date(), updatedAt: new Date(), - ...(enrollments.length > 0 && { - topics: enrollments.map((enrollment) => ({ - id: enrollment.id, - title: enrollment.course.title, - description: enrollment.course.description, - level: enrollment.course.level, - createdAt: enrollment.createdAt, - updatedAt: enrollment.updatedAt, - })), - }), + + topics: + enrollments.length > 0 + ? enrollments.map((enrollment) => ({ + id: enrollment.id, + title: enrollment.course.title, + description: enrollment.course.description, + level: enrollment.course.level, + createdAt: enrollment.createdAt, + updatedAt: enrollment.updatedAt, + })) + : [], }; + console.log(requestBody); + + console.log(enrollments); + const response = await this.httpService.axiosRef.post( `${this.configService.get( GLOBAL_CONFIG.AI_URL, @@ -332,7 +339,7 @@ export class ExamService { const createQuestionDto = { examId, question: data.question, - type: QuestionType.PRETEST, + type: QuestionType.MULTIPLE_CHOICE, points: 1, orderIndex: orderIndex++, }; diff --git a/src/question-option/question-option.controller.ts b/src/question-option/question-option.controller.ts index fea7f99..e97e4e0 100644 --- a/src/question-option/question-option.controller.ts +++ b/src/question-option/question-option.controller.ts @@ -200,10 +200,10 @@ export class QuestionOptionController { @Roles(Role.TEACHER) @Roles(Role.ADMIN) @ApiResponse({ - status: HttpStatus.NO_CONTENT, + status: HttpStatus.OK, description: 'Delete a question option', + type: QuestionOptionResponseDto, }) - @HttpCode(HttpStatus.NO_CONTENT) async deleteExam( @Req() request: AuthenticatedRequest, @Param( diff --git a/src/question-option/question-option.service.ts b/src/question-option/question-option.service.ts index 6014a7c..514ed2c 100644 --- a/src/question-option/question-option.service.ts +++ b/src/question-option/question-option.service.ts @@ -295,8 +295,7 @@ export class QuestionOptionService { }); if (this.checkPermission(userId, role, questionOption) === false) throw new ForbiddenException('Can not change this question option'); - await this.questionOptionRepository.delete(id); - return questionOption; + return await this.questionOptionRepository.remove(questionOption); } catch (error) { if (error instanceof Error) throw new NotFoundException('Question option not found'); diff --git a/src/question/question.controller.ts b/src/question/question.controller.ts index a4a5db9..1569403 100644 --- a/src/question/question.controller.ts +++ b/src/question/question.controller.ts @@ -198,10 +198,10 @@ export class QuestionController { @Roles(Role.TEACHER) @Roles(Role.ADMIN) @ApiResponse({ - status: HttpStatus.NO_CONTENT, + status: HttpStatus.OK, description: 'Delete an question', + type: QuestionResponseDto, }) - @HttpCode(HttpStatus.NO_CONTENT) async deleteExam( @Req() request: AuthenticatedRequest, @Param( diff --git a/src/question/question.service.ts b/src/question/question.service.ts index 2ea8a5d..dcc8a03 100644 --- a/src/question/question.service.ts +++ b/src/question/question.service.ts @@ -355,9 +355,9 @@ export class QuestionService { const question = await this.findOne(userId, role, { where: { id } }); if (this.checkPermission(userId, role, question) === false) throw new BadRequestException('Can not change this question'); - await this.questionRepository.delete(id); + const removeQuestion = await this.questionRepository.remove(question); await this.reOrderIndex(question.examId); - return question; + return removeQuestion; } catch (error) { if (error instanceof Error) throw new NotFoundException('Question not found'); diff --git a/src/shared/enums/question-type.enum.ts b/src/shared/enums/question-type.enum.ts index b5ce356..f657d20 100644 --- a/src/shared/enums/question-type.enum.ts +++ b/src/shared/enums/question-type.enum.ts @@ -2,5 +2,4 @@ export enum QuestionType { MULTIPLE_CHOICE = 'multiple_choice', TRUE_FALSE = 'true_false', ESSAY = 'essay', - PRETEST = 'pretest', } From 1dcc4a52fc962995806a092065c1f4908cbb6346 Mon Sep 17 00:00:00 2001 From: khris-xp Date: Tue, 26 Nov 2024 14:48:09 +0700 Subject: [PATCH 114/155] fix: get course by course module id validate --- src/course-module/course-module.service.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/course-module/course-module.service.ts b/src/course-module/course-module.service.ts index f42b842..ce272a5 100644 --- a/src/course-module/course-module.service.ts +++ b/src/course-module/course-module.service.ts @@ -77,11 +77,7 @@ export class CourseModuleService { course: true, }, }); - - if (!courseModules.length) { - throw new NotFoundException('Course modules not found or course is not published'); - } - + return courseModules; } async validateAndGetNextOrderIndex(courseId: string): Promise { From 195c5a7142f677298d011a58e4cd56a98966beaa Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Tue, 26 Nov 2024 17:39:10 +0700 Subject: [PATCH 115/155] feat: create pretest and find question pretest --- src/app.module.ts | 2 + src/enrollment/enrollment.service.ts | 7 - src/exam-attempt/exam-attempt.entity.ts | 15 +- src/exam/dtos/create-exam.dto.ts | 2 +- src/exam/exam.controller.ts | 31 +- src/exam/exam.entity.ts | 2 +- src/exam/exam.module.ts | 18 +- src/exam/exam.service.ts | 120 +------- src/pretest/dtos/create-pretest.dto.ts | 46 +++ src/pretest/dtos/pretest-response.dto.ts | 104 +++++++ src/{exam => pretest}/dtos/pretest.dto.ts | 0 src/pretest/dtos/update-pretest.dto.ts | 4 + src/pretest/pretest.controller.ts | 182 +++++++++++ src/pretest/pretest.entity.ts | 75 +++++ src/pretest/pretest.module.ts | 30 ++ src/pretest/pretest.providers.ts | 10 + src/pretest/pretest.service.ts | 289 ++++++++++++++++++ src/question/dtos/create-question.dto.ts | 50 ++- .../dtos/question-pretest-response.dto.ts | 109 +++++++ src/question/dtos/question-response.dto.ts | 2 +- src/question/dtos/update-question.dto.ts | 9 +- src/question/question.controller.ts | 98 ++++++ src/question/question.entity.ts | 19 +- src/question/question.module.ts | 6 +- src/question/question.service.ts | 166 +++++++++- src/shared/configs/database.config.ts | 2 + src/shared/enums/question-type.enum.ts | 1 + .../user-background.service.ts | 24 +- src/user/user.entity.ts | 9 +- 29 files changed, 1240 insertions(+), 192 deletions(-) create mode 100644 src/pretest/dtos/create-pretest.dto.ts create mode 100644 src/pretest/dtos/pretest-response.dto.ts rename src/{exam => pretest}/dtos/pretest.dto.ts (100%) create mode 100644 src/pretest/dtos/update-pretest.dto.ts create mode 100644 src/pretest/pretest.controller.ts create mode 100644 src/pretest/pretest.entity.ts create mode 100644 src/pretest/pretest.module.ts create mode 100644 src/pretest/pretest.providers.ts create mode 100644 src/pretest/pretest.service.ts create mode 100644 src/question/dtos/question-pretest-response.dto.ts diff --git a/src/app.module.ts b/src/app.module.ts index 33255b5..f6adadd 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -34,6 +34,7 @@ import { UserOccupationModule } from './user-occupation/user-occupation.module'; import { UserStreakModule } from './user-streak/user-streak.module'; import { UserModule } from './user/user.module'; import { UserRewardModule } from './user-reward/user-reward.module'; +import { PretestModule } from './pretest/pretest.module'; @Module({ imports: [ @@ -79,6 +80,7 @@ import { UserRewardModule } from './user-reward/user-reward.module'; RewardModule, UserRewardModule, RoadmapModule, + PretestModule, ], controllers: [AppController], providers: [ diff --git a/src/enrollment/enrollment.service.ts b/src/enrollment/enrollment.service.ts index 764d5e9..815c94d 100644 --- a/src/enrollment/enrollment.service.ts +++ b/src/enrollment/enrollment.service.ts @@ -42,13 +42,6 @@ export class EnrollmentService { return enrollments; } - async findEnrollmentByUserId(userId: string): Promise { - const enrollment = this.enrollmentRepository.find({ - where: { user: { id: userId } }, - }); - return enrollment; - } - async findOne(where: FindOptionsWhere): Promise { const options: FindOneOptions = { where, diff --git a/src/exam-attempt/exam-attempt.entity.ts b/src/exam-attempt/exam-attempt.entity.ts index 440723a..4fa8bcf 100644 --- a/src/exam-attempt/exam-attempt.entity.ts +++ b/src/exam-attempt/exam-attempt.entity.ts @@ -1,5 +1,6 @@ import { ExamAnswer } from 'src/exam-answer/exam-answer.entity'; import { Exam } from 'src/exam/exam.entity'; +import { Pretest } from 'src/pretest/pretest.entity'; import { ExamAttemptStatus } from 'src/shared/enums'; import { User } from 'src/user/user.entity'; import { @@ -21,14 +22,24 @@ export class ExamAttempt { @ManyToOne(() => Exam, (exam) => exam.examAttempt, { onDelete: 'CASCADE', - nullable: false, + nullable: true, }) @JoinColumn({ name: 'exam_id' }) exam: Exam; - @Column({ name: 'exam_id' }) + @Column({ name: 'exam_id', nullable: true }) examId: String; + @ManyToOne(() => Pretest, (pretest) => pretest.examAttempt, { + onDelete: 'CASCADE', + nullable: true, + }) + @JoinColumn({ name: 'pretest_id' }) + pretest: Pretest; + + @Column({ name: 'pretest_id', nullable: true }) + pretestId: String; + @ManyToOne(() => User, (user) => user.examAttempt, { onDelete: 'CASCADE', nullable: false, diff --git a/src/exam/dtos/create-exam.dto.ts b/src/exam/dtos/create-exam.dto.ts index 47919e0..08cf1af 100644 --- a/src/exam/dtos/create-exam.dto.ts +++ b/src/exam/dtos/create-exam.dto.ts @@ -39,7 +39,7 @@ export class CreateExamDto { type: Number, example: 20, }) - timeLimit: number = 20; + timeLimit: number; @IsNotEmpty() @IsInt() diff --git a/src/exam/exam.controller.ts b/src/exam/exam.controller.ts index 10d357f..effb96a 100644 --- a/src/exam/exam.controller.ts +++ b/src/exam/exam.controller.ts @@ -31,11 +31,6 @@ import { CreateExamDto } from './dtos/create-exam.dto'; import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { UpdateExamDto } from './dtos/update-exam.dto'; -import { - PaginatedQuestionResponseDto, - QuestionResponseDto, -} from 'src/question/dtos/question-response.dto'; -import { Public } from 'src/shared/decorators/public.decorator'; @Controller('exam') @ApiTags('Exam') @@ -48,7 +43,7 @@ export class ExamController { @ApiResponse({ status: HttpStatus.OK, description: 'Returns all exams', - type: PaginatedQuestionResponseDto, + type: PaginatedExamResponseDto, isArray: true, }) @ApiQuery({ @@ -174,28 +169,4 @@ export class ExamController { ); return new ExamResponseDto(exam); } - - @Post('generate/:examId') - @ApiResponse({ - status: HttpStatus.CREATED, - description: 'Create an question and question option in exam', - }) - @HttpCode(HttpStatus.CREATED) - async createQuestionAndChoice( - @Req() request: AuthenticatedRequest, - @Param( - 'examId', - new ParseUUIDPipe({ - version: '4', - errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, - }), - ) - examId: string, - ): Promise { - const questions = await this.examService.createQuestionAndChoice( - examId, - request.user.id, - ); - return questions.map((question) => new QuestionResponseDto(question)); - } } diff --git a/src/exam/exam.entity.ts b/src/exam/exam.entity.ts index 6bc2aaf..c173b42 100644 --- a/src/exam/exam.entity.ts +++ b/src/exam/exam.entity.ts @@ -52,7 +52,7 @@ export class Exam { nullable: false, default: 20, }) - timeLimit: number; + timeLimit: number = 20; @Column({ nullable: false, diff --git a/src/exam/exam.module.ts b/src/exam/exam.module.ts index 73d7cb7..39b0384 100644 --- a/src/exam/exam.module.ts +++ b/src/exam/exam.module.ts @@ -6,25 +6,9 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { Exam } from './exam.entity'; import { examProviders } from './exam.providers'; import { CourseModule } from 'src/course-module/course-module.entity'; -import { QuestionModule } from 'src/question/question.module'; -import { QuestionOptionModule } from 'src/question-option/question-option.module'; -import { ExamAnswerModule } from 'src/exam-answer/exam-answer.module'; -import { HttpModule } from '@nestjs/axios'; -import { UserModule } from 'src/user/user.module'; -import { EnrollmentModule } from 'src/enrollment/enrollment.module'; -import { ConfigModule } from '@nestjs/config'; @Module({ - imports: [ - DatabaseModule, - TypeOrmModule.forFeature([Exam, CourseModule]), - QuestionModule, - QuestionOptionModule, - HttpModule, - UserModule, - EnrollmentModule, - ConfigModule, - ], + imports: [DatabaseModule, TypeOrmModule.forFeature([Exam, CourseModule])], controllers: [ExamController], providers: [...examProviders, ExamService], exports: [ExamService], diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts index 8128ed9..59ecc3e 100644 --- a/src/exam/exam.service.ts +++ b/src/exam/exam.service.ts @@ -16,19 +16,9 @@ import { Exam } from './exam.entity'; import { CreateExamDto } from './dtos/create-exam.dto'; import { PaginatedExamResponseDto } from './dtos/exam-response.dto'; import { createPagination } from 'src/shared/pagination'; -import { CourseStatus, ExamStatus, QuestionType, Role } from 'src/shared/enums'; +import { CourseStatus, ExamStatus, Role } from 'src/shared/enums'; import { UpdateExamDto } from './dtos/update-exam.dto'; import { CourseModule } from 'src/course-module/course-module.entity'; -import { QuestionService } from 'src/question/question.service'; -import { QuestionOptionService } from 'src/question-option/question-option.service'; -import { HttpService } from '@nestjs/axios'; -import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; -import { UserService } from 'src/user/user.service'; -import { EnrollmentService } from 'src/enrollment/enrollment.service'; -import { PretestDto } from './dtos/pretest.dto'; -import { Question } from 'src/question/question.entity'; -import { ConfigService } from '@nestjs/config'; -import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; @Injectable() export class ExamService { @@ -37,12 +27,6 @@ export class ExamService { private readonly examRepository: Repository, @Inject('CourseModuleRepository') private readonly courseModuleRepository: Repository, - private readonly questionService: QuestionService, - private readonly questionOptionService: QuestionOptionService, - private readonly httpService: HttpService, - private readonly userService: UserService, - private readonly enrollService: EnrollmentService, - private readonly configService: ConfigService, ) {} async findAll( @@ -213,8 +197,6 @@ export class ExamService { examInData.status != ExamStatus.DRAFT && updateExamDto.status == ExamStatus.DRAFT ) { - console.log(examInData.status); - console.log(updateExamDto.status); throw new ForbiddenException("Can't change status to draft"); } @@ -269,104 +251,4 @@ export class ExamService { return false; } } - - async fetchData(examId: string, userId: string): Promise { - const user = await this.userService.findOne({ where: { id: userId } }); - if (!user) throw new NotFoundException('Not Found User'); - const exam = await this.examRepository.findOne({ where: { id: examId } }); - if (!exam) throw new NotFoundException('Not Found this exam'); - const enrollments = await this.enrollService.findEnrollmentByUserId(userId); - try { - const requestBody = { - id: exam.id, - user: { - id: user.id, - email: user.email, - points: user.points, - role: user.role, - createdAt: user.createdAt, - updatedAt: user.updatedAt, - fullname: user.fullname, - }, - occupation: { - id: exam.id, - title: exam.title, - description: exam.description, - createdAt: exam.createdAt, - updatedAt: exam.updatedAt, - }, - createdAt: new Date(), - updatedAt: new Date(), - - topics: - enrollments.length > 0 - ? enrollments.map((enrollment) => ({ - id: enrollment.id, - title: enrollment.course.title, - description: enrollment.course.description, - level: enrollment.course.level, - createdAt: enrollment.createdAt, - updatedAt: enrollment.updatedAt, - })) - : [], - }; - - console.log(requestBody); - - console.log(enrollments); - - const response = await this.httpService.axiosRef.post( - `${this.configService.get( - GLOBAL_CONFIG.AI_URL, - )}/generate-pretest/`, - requestBody, - ); - return { data: response.data }; - } catch (error) { - throw new Error('Failed to fetch data or process request'); - } - } - - async createQuestionAndChoice( - examId: string, - userId: string, - ): Promise { - let questions = []; - const fetchData = await this.fetchData(examId, userId); - let orderIndex = (await this.questionService.getMaxOrderIndex(examId)) + 1; - await Promise.all( - fetchData.data.map(async (data) => { - const createQuestionDto = { - examId, - question: data.question, - type: QuestionType.MULTIPLE_CHOICE, - points: 1, - orderIndex: orderIndex++, - }; - - const question = await this.questionService.createQuestion( - createQuestionDto, - ); - - questions.push(question); - - await Promise.all( - Object.entries(data.choices).map(([key, value]) => { - const createQuestionOptionDto = { - questionId: question.id, - optionText: `${key}. ${value}`, - isCorrect: key === data.answer, - explanation: '', - }; - - return this.questionOptionService.createQuestionOption( - createQuestionOptionDto, - ); - }), - ); - }), - ); - - return questions; - } } diff --git a/src/pretest/dtos/create-pretest.dto.ts b/src/pretest/dtos/create-pretest.dto.ts new file mode 100644 index 0000000..daecd40 --- /dev/null +++ b/src/pretest/dtos/create-pretest.dto.ts @@ -0,0 +1,46 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsInt, IsNotEmpty, IsOptional } from 'class-validator'; +export class CreatePretestDto { + @IsNotEmpty() + @ApiProperty({ + description: 'Exam title', + type: String, + example: 'Biology', + }) + title: string; + + @IsOptional() + @ApiProperty({ + description: 'Exam description', + type: String, + example: 'This course is an introduction to biology', + }) + description?: string; + + @IsNotEmpty() + @IsInt() + @ApiProperty({ + description: 'time limit to do exam.', + type: Number, + example: 20, + }) + timeLimit: number; + + @IsNotEmpty() + @IsInt() + @ApiProperty({ + description: 'Score to pass exam.', + type: Number, + example: 3, + }) + passingScore: number; + + @IsNotEmpty() + @IsInt() + @ApiProperty({ + description: 'Max attempts to do exam.', + type: Number, + example: 1, + }) + maxAttempts: number; +} diff --git a/src/pretest/dtos/pretest-response.dto.ts b/src/pretest/dtos/pretest-response.dto.ts new file mode 100644 index 0000000..e8c620d --- /dev/null +++ b/src/pretest/dtos/pretest-response.dto.ts @@ -0,0 +1,104 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { UserResponseDto } from 'src/user/dtos/user-response.dto'; +import { Pretest } from '../pretest.entity'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; + +export class PretestResponseDto { + @ApiProperty({ + description: 'Pretest ID', + type: String, + example: '123e4567-e89b-12d3-a456-426614174000', + }) + id: string; + + @ApiProperty({ + description: 'User Data', + type: String, + example: { + id: '389d1065-b898-4249-9d3d-e17e100336a7', + username: 'johndoe', + fullname: 'John Doe', + role: 'student', + email: 'johndoe@gmail.com', + profileKey: null, + }, + }) + user: UserResponseDto; + + @ApiProperty({ + description: 'Exam title', + type: String, + example: 'Exam title', + }) + title: string; + + @ApiProperty({ + description: 'Exam description', + type: String, + example: 'Exam description', + }) + description: string; + + @ApiProperty({ + description: 'Timelimit to do exam.', + type: Number, + example: 20, + }) + timeLimit: Number; + + @ApiProperty({ + description: 'Score to pass exam.', + type: Number, + example: 3, + }) + passingScore: Number; + + @ApiProperty({ + description: 'Max attempts to do exam.', + type: Number, + example: 1, + }) + maxAttempts: Number; + + @ApiProperty({ + description: 'Exam created date', + type: Date, + example: new Date(), + }) + createdAt: Date; + + @ApiProperty({ + description: 'Exam updated date', + type: Date, + example: new Date(), + }) + updatedAt: Date; + + constructor(pretest: Pretest) { + this.id = pretest.id; + this.user = pretest.user; + this.title = pretest.title; + this.description = pretest.description; + this.timeLimit = pretest.timeLimit; + this.passingScore = pretest.passingScore; + this.maxAttempts = pretest.maxAttempts; + this.createdAt = pretest.createdAt; + this.updatedAt = pretest.updatedAt; + } +} + +export class PaginatedPretestResponseDto extends PaginatedResponse( + PretestResponseDto, +) { + constructor( + pretest: Pretest[], + total: number, + pageSize: number, + currentPage: number, + ) { + const pretestDtos = pretest.map( + (pretest) => new PretestResponseDto(pretest), + ); + super(pretestDtos, total, pageSize, currentPage); + } +} diff --git a/src/exam/dtos/pretest.dto.ts b/src/pretest/dtos/pretest.dto.ts similarity index 100% rename from src/exam/dtos/pretest.dto.ts rename to src/pretest/dtos/pretest.dto.ts diff --git a/src/pretest/dtos/update-pretest.dto.ts b/src/pretest/dtos/update-pretest.dto.ts new file mode 100644 index 0000000..3a72746 --- /dev/null +++ b/src/pretest/dtos/update-pretest.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreatePretestDto } from './create-pretest.dto'; + +export class UpdatePretestDto extends PartialType(CreatePretestDto) {} diff --git a/src/pretest/pretest.controller.ts b/src/pretest/pretest.controller.ts new file mode 100644 index 0000000..5114e7b --- /dev/null +++ b/src/pretest/pretest.controller.ts @@ -0,0 +1,182 @@ +import { + Body, + Controller, + Delete, + Get, + HttpCode, + HttpStatus, + Injectable, + Param, + ParseUUIDPipe, + Patch, + Post, + Query, + Req, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { PretestService } from './pretest.service'; +import { + PaginatedPretestResponseDto, + PretestResponseDto, +} from './dtos/pretest-response.dto'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; +import { Roles } from 'src/shared/decorators/role.decorator'; +import { Role } from 'src/shared/enums'; +import { CreatePretestDto } from './dtos/create-pretest.dto'; +import { UpdatePretestDto } from './dtos/update-pretest.dto'; + +@Controller('pretest') +@ApiTags('Pretest') +@ApiBearerAuth() +@Injectable() +export class PretestController { + constructor(private readonly pretestService: PretestService) {} + @Get() + @Roles(Role.STUDENT) + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all pretests', + type: PaginatedPretestResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findAll( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return await this.pretestService.findAll( + request.user.id, + request.user.role, + { + page: query.page, + limit: query.limit, + search: query.search, + }, + ); + } + + @Get(':id') + @Roles(Role.STUDENT) + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns a pretest', + type: PretestResponseDto, + }) + async findOne( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const pretest = await this.pretestService.findOne( + request.user.id, + request.user.role, + { where: { id } }, + ); + return new PretestResponseDto(pretest); + } + + @Post() + @Roles(Role.STUDENT) + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Create a pretest', + type: PretestResponseDto, + }) + @HttpCode(HttpStatus.CREATED) + async createPretest( + @Req() request: AuthenticatedRequest, + @Body() createPretestDto: CreatePretestDto, + ): Promise { + const pretest = await this.pretestService.createPretest( + request.user.id, + createPretestDto, + ); + return new PretestResponseDto(pretest); + } + + @Patch(':id') + @Roles(Role.STUDENT) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Update a pretest', + type: PretestResponseDto, + }) + async updatePretest( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @Body() updatePretestDto: UpdatePretestDto, + ): Promise { + const pretest = await this.pretestService.updatePretest( + request.user.id, + request.user.role, + id, + updatePretestDto, + ); + return new PretestResponseDto(pretest); + } + + @Delete(':id') + @Roles(Role.STUDENT) + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Delete a pretest', + type: PretestResponseDto, + }) + async deleteExam( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const pretest = await this.pretestService.deletePretest( + request.user.id, + request.user.role, + id, + ); + return new PretestResponseDto(pretest); + } + + @Get('pretest/test') + async test(@Req() request: AuthenticatedRequest): Promise { + this.pretestService.fetchData(request.user.id); + } +} diff --git a/src/pretest/pretest.entity.ts b/src/pretest/pretest.entity.ts new file mode 100644 index 0000000..192e926 --- /dev/null +++ b/src/pretest/pretest.entity.ts @@ -0,0 +1,75 @@ +import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; +import { Question } from 'src/question/question.entity'; +import { User } from 'src/user/user.entity'; +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + OneToMany, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; + +@Entity() +export class Pretest { + @PrimaryGeneratedColumn('uuid') + id: string; + + @ManyToOne(() => User, (user) => user.pretest, { + onDelete: 'CASCADE', + nullable: false, + }) + @JoinColumn({ name: 'user_id' }) + user: User; + + @Column({ name: 'user_id' }) + userId: String; + + @OneToMany(() => ExamAttempt, (examAttempt) => examAttempt.pretest, { + cascade: true, + }) + examAttempt: ExamAttempt[]; + + @OneToMany(() => Question, (question) => question.pretest, { + cascade: true, + }) + question: Question[]; + + @Column({ + nullable: false, + }) + title: string; + + @Column({ + nullable: true, + }) + description: string; + + @Column({ + nullable: false, + default: 20, + }) + timeLimit: number = 20; + + @Column({ + nullable: false, + }) + passingScore: number; + + @Column({ + nullable: false, + }) + maxAttempts: number; + + @CreateDateColumn({ + type: 'timestamp with time zone', + }) + createdAt: Date; + + @UpdateDateColumn({ + type: 'timestamp with time zone', + }) + updatedAt: Date; +} diff --git a/src/pretest/pretest.module.ts b/src/pretest/pretest.module.ts new file mode 100644 index 0000000..70f6251 --- /dev/null +++ b/src/pretest/pretest.module.ts @@ -0,0 +1,30 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { DatabaseModule } from 'src/database/database.module'; +import { Pretest } from './pretest.entity'; +import { PretestController } from './pretest.controller'; +import { pretestProviders } from './pretest.providers'; +import { PretestService } from './pretest.service'; +import { User } from 'src/user/user.entity'; +import { UserModule } from 'src/user/user.module'; +import { UserBackgroundModule } from 'src/user-background/user-background.module'; +import { HttpModule } from '@nestjs/axios'; +import { ConfigModule } from '@nestjs/config'; +import { QuestionModule } from 'src/question/question.module'; +import { QuestionOptionModule } from 'src/question-option/question-option.module'; + +@Module({ + imports: [ + DatabaseModule, + TypeOrmModule.forFeature([Pretest, User]), + UserBackgroundModule, + HttpModule, + ConfigModule, + QuestionModule, + QuestionOptionModule, + ], + controllers: [PretestController], + providers: [...pretestProviders, PretestService], + exports: [PretestService], +}) +export class PretestModule {} diff --git a/src/pretest/pretest.providers.ts b/src/pretest/pretest.providers.ts new file mode 100644 index 0000000..9eb7021 --- /dev/null +++ b/src/pretest/pretest.providers.ts @@ -0,0 +1,10 @@ +import { DataSource } from 'typeorm'; +import { Pretest } from './pretest.entity'; + +export const pretestProviders = [ + { + provide: 'PretestRepository', + useFactory: (dataSource: DataSource) => dataSource.getRepository(Pretest), + inject: ['DataSource'], + }, +]; diff --git a/src/pretest/pretest.service.ts b/src/pretest/pretest.service.ts new file mode 100644 index 0000000..7f7b89c --- /dev/null +++ b/src/pretest/pretest.service.ts @@ -0,0 +1,289 @@ +import { + BadRequestException, + Inject, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { Pretest } from './pretest.entity'; +import { + FindOneOptions, + FindOptionsSelect, + FindOptionsWhere, + ILike, + Repository, +} from 'typeorm'; +import { PaginatedPretestResponseDto } from './dtos/pretest-response.dto'; +import { createPagination } from 'src/shared/pagination'; +import { User } from 'src/user/user.entity'; +import { QuestionType, Role } from 'src/shared/enums'; +import { CreatePretestDto } from './dtos/create-pretest.dto'; +import { UpdatePretestDto } from './dtos/update-pretest.dto'; +import { UserService } from 'src/user/user.service'; +import { UserBackgroundService } from 'src/user-background/user-background.service'; +import { HttpService } from '@nestjs/axios'; +import { ConfigService } from '@nestjs/config'; +import { PretestDto } from './dtos/pretest.dto'; +import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; +import { QuestionService } from 'src/question/question.service'; +import { QuestionOptionService } from 'src/question-option/question-option.service'; + +@Injectable() +export class PretestService { + constructor( + @Inject('PretestRepository') + private readonly pretestRepository: Repository, + @Inject('UserRepository') + private readonly userRepository: Repository, + private readonly userBackgroundService: UserBackgroundService, + private readonly httpService: HttpService, + private readonly configService: ConfigService, + private readonly questionService: QuestionService, + private readonly questionOptionService: QuestionOptionService, + ) {} + async findAll( + userId: string, + role: Role, + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.pretestRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateCondition( + userId, + role, + search, + ); + const pretest = await find({ + where: whereCondition, + relations: ['user'], + select: { + user: this.selectPopulateUser(), + }, + }).run(); + + return new PaginatedPretestResponseDto( + pretest.data, + pretest.meta.total, + pretest.meta.pageSize, + pretest.meta.currentPage, + ); + } + + async findOne( + userId: string, + role: Role, + options: FindOneOptions = {}, + ): Promise { + const whereCondition = this.validateAndCreateCondition(userId, role, ''); + + const where = Array.isArray(whereCondition) + ? [ + { ...whereCondition[0], ...options.where }, + { ...whereCondition[1], ...options.where }, + ] + : { ...whereCondition, ...options.where }; + + const pretest = await this.pretestRepository.findOne({ + ...options, + where, + relations: ['user'], + select: { + user: this.selectPopulateUser(), + }, + }); + + if (!pretest) { + throw new NotFoundException('Pretest not found'); + } + + return pretest; + } + + private validateAndCreateCondition( + userId: string, + role: Role, + search: string, + ): FindOptionsWhere | FindOptionsWhere[] { + const baseSearch = search ? { title: ILike(`%${search}%`) } : {}; + + if (role === Role.ADMIN) { + return { + ...baseSearch, + }; + } + if (role === Role.STUDENT) { + return { + ...baseSearch, + user: { + id: userId, + }, + }; + } + return { + ...baseSearch, + user: { + id: userId, + }, + }; + } + + async createPretest( + userId: string, + createPretestDto: CreatePretestDto, + ): Promise { + const user = await this.userRepository.findOne({ + where: { id: userId }, + select: this.selectPopulateUser(), + }); + + if (!user) throw new NotFoundException('User not found'); + + const pretest = await this.pretestRepository.create({ + ...createPretestDto, + user, + }); + + if (!pretest) throw new BadRequestException("Can't create pretest"); + await this.pretestRepository.save(pretest); + await this.createQuestionAndChoice(pretest.id, userId); + return pretest; + } + + async updatePretest( + userId: string, + role: Role, + id: string, + updatePretestDto: UpdatePretestDto, + ): Promise { + const pretestInData = await this.findOne(userId, role, { where: { id } }); + + const pretest = await this.pretestRepository.update(id, updatePretestDto); + if (!pretest) throw new BadRequestException("Can't update pretest"); + return await this.pretestRepository.findOne({ + where: { id }, + relations: ['user'], + select: { + user: this.selectPopulateUser(), + }, + }); + } + + async deletePretest( + userId: string, + role: Role, + id: string, + ): Promise { + try { + const pretest = await this.findOne(userId, role, { where: { id } }); + return await this.pretestRepository.remove(pretest); + } catch (error) { + if (error instanceof Error) + throw new NotFoundException('Pretest not found'); + } + } + + private selectPopulateUser(): FindOptionsSelect { + return { + id: true, + username: true, + fullname: true, + role: true, + email: true, + profileKey: true, + }; + } + + async fetchData(userId: string): Promise { + const userBackground = await this.userBackgroundService.findOneByUserId( + userId, + ); + try { + const requestBody = { + id: '1', + user: { + id: userBackground.user.id, + email: userBackground.user.email, + points: userBackground.user.points, + role: userBackground.user.role, + createdAt: userBackground.user.createdAt, + updatedAt: userBackground.user.updatedAt, + fullname: userBackground.user.fullname, + }, + occupation: { + id: userBackground.occupation.id, + title: userBackground.occupation.title, + description: userBackground.occupation.description, + createdAt: userBackground.occupation.createdAt, + updatedAt: userBackground.occupation.updatedAt, + }, + createdAt: new Date(), + updatedAt: new Date(), + // topics: + // userBackground.topics.length > 0 + // ? userBackground.topics.map((topic) => ({ + // id: topic.id, + // title: topic.title, + // description: topic.description, + // level: topic.level, + // createdAt: topic.createdAt, + // updatedAt: topic.updatedAt, + // })) + // : [], + }; + const response = await this.httpService.axiosRef.post( + `${this.configService.get( + GLOBAL_CONFIG.AI_URL, + )}/generate-pretest/`, + requestBody, + ); + return { data: response.data }; + } catch (error) { + throw new Error('Failed to fetch data or process request'); + } + } + + async createQuestionAndChoice( + pretestId: string, + userId: string, + ): Promise { + const fetchData = await this.fetchData(userId); + let orderIndex = 1; + await Promise.all( + fetchData.data.map(async (data) => { + const createQuestionDto = { + pretestId, + question: data.question, + type: QuestionType.PRETEST, + points: 1, + orderIndex: orderIndex++, + }; + const question = await this.questionService.createQuestionForPretest( + createQuestionDto, + ); + await Promise.all( + Object.entries(data.choices).map(([key, value]) => { + const createQuestionOptionDto = { + questionId: question.id, + optionText: `${key}. ${value}`, + isCorrect: key === data.answer, + explanation: '', + }; + return this.questionOptionService.createQuestionOption( + createQuestionOptionDto, + ); + }), + ); + }), + ); + } +} diff --git a/src/question/dtos/create-question.dto.ts b/src/question/dtos/create-question.dto.ts index 552a002..125aae6 100644 --- a/src/question/dtos/create-question.dto.ts +++ b/src/question/dtos/create-question.dto.ts @@ -36,7 +36,55 @@ export class CreateQuestionDto { type: Number, example: 1, }) - points: number = 1; + points: number; + + @IsOptional() + @IsInt() + @Min(1) + @ApiProperty({ + description: 'Order question', + type: Number, + example: 1, + }) + orderIndex?: number; +} + +export class CreateQuestionPretestDto { + @IsNotEmpty() + @ApiProperty({ + description: 'Pretest ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + pretestId: string; + + @IsNotEmpty() + @ApiProperty({ + description: 'Question exam', + type: String, + example: 'What is this?', + }) + question: string; + + @IsNotEmpty() + @IsEnum(QuestionType) + @ApiProperty({ + description: 'Type question', + type: String, + example: QuestionType.PRETEST, + enum: QuestionType, + }) + type: QuestionType; + + @IsNotEmpty() + @IsInt() + @Min(1) + @ApiProperty({ + description: 'Points in 1 question', + type: Number, + example: 1, + }) + points: number; @IsOptional() @IsInt() diff --git a/src/question/dtos/question-pretest-response.dto.ts b/src/question/dtos/question-pretest-response.dto.ts new file mode 100644 index 0000000..8cef109 --- /dev/null +++ b/src/question/dtos/question-pretest-response.dto.ts @@ -0,0 +1,109 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { Question } from '../question.entity'; +import { QuestionType } from 'src/shared/enums'; +import { Pretest } from 'src/pretest/pretest.entity'; +import { PretestResponseDto } from 'src/pretest/dtos/pretest-response.dto'; + +export class QuestionPretestResponseDto { + @ApiProperty({ + description: 'Question ID', + type: String, + example: '123e4567-e89b-12d3-a456-426614174000', + }) + id: string; + + @ApiProperty({ + description: 'Pretest Data', + type: PretestResponseDto, + example: { + id: '80ff2ec3-7c6d-4427-a98d-58ac3aa68697', + user: { + id: 'a12e1e37-3504-4711-a389-09a1734d7b1c', + username: 'johndoe', + fullname: 'John Doe', + role: 'student', + email: 'johndoe@gmail.com', + profileKey: null, + }, + title: 'Biology', + description: 'This course is an introduction to biology', + timeLimit: 20, + passingScore: 3, + maxAttempts: 1, + createdAt: '2024-11-26T10:06:39.408Z', + updatedAt: '2024-11-26T10:06:39.408Z', + }, + }) + pretest: Pretest; + + @ApiProperty({ + description: 'Pretest question', + type: String, + example: 'What is this?', + }) + question: string; + + @ApiProperty({ + description: 'Type question pretest', + type: String, + example: QuestionType.PRETEST, + enum: QuestionType, + }) + type: QuestionType; + + @ApiProperty({ + description: 'Points in 1 question', + type: Number, + example: 0, + }) + points: Number; + + @ApiProperty({ + description: 'Index in exam.', + type: Number, + example: 1, + }) + orderIndex: Number; + + @ApiProperty({ + description: 'Exam created date', + type: Date, + example: new Date(), + }) + createdAt: Date; + + @ApiProperty({ + description: 'Exam updated date', + type: Date, + example: new Date(), + }) + updatedAt: Date; + + constructor(question: Question) { + this.id = question.id; + this.pretest = question.pretest; + this.question = question.question; + this.type = question.type; + this.points = question.points; + this.orderIndex = question.orderIndex; + this.createdAt = question.createdAt; + this.updatedAt = question.updatedAt; + } +} + +export class PaginatedQuestionPretestResponseDto extends PaginatedResponse( + QuestionPretestResponseDto, +) { + constructor( + question: Question[], + total: number, + pageSize: number, + currentPage: number, + ) { + const questionDtos = question.map( + (question) => new QuestionPretestResponseDto(question), + ); + super(questionDtos, total, pageSize, currentPage); + } +} diff --git a/src/question/dtos/question-response.dto.ts b/src/question/dtos/question-response.dto.ts index 407ae81..d79fd9e 100644 --- a/src/question/dtos/question-response.dto.ts +++ b/src/question/dtos/question-response.dto.ts @@ -61,7 +61,7 @@ export class QuestionResponseDto { points: Number; @ApiProperty({ - description: 'Score to pass exam.', + description: 'Index in exam.', type: Number, example: 1, }) diff --git a/src/question/dtos/update-question.dto.ts b/src/question/dtos/update-question.dto.ts index 94c4a43..41ee21f 100644 --- a/src/question/dtos/update-question.dto.ts +++ b/src/question/dtos/update-question.dto.ts @@ -1,6 +1,13 @@ import { OmitType, PartialType } from '@nestjs/swagger'; -import { CreateQuestionDto } from './create-question.dto'; +import { + CreateQuestionDto, + CreateQuestionPretestDto, +} from './create-question.dto'; export class UpdateQuestionDto extends PartialType( OmitType(CreateQuestionDto, ['examId'] as const), ) {} + +export class UpdateQuestionPretestDto extends PartialType( + OmitType(CreateQuestionPretestDto, ['pretestId'] as const), +) {} diff --git a/src/question/question.controller.ts b/src/question/question.controller.ts index 1569403..fdc93a3 100644 --- a/src/question/question.controller.ts +++ b/src/question/question.controller.ts @@ -25,6 +25,10 @@ import { Roles } from 'src/shared/decorators/role.decorator'; import { Role } from 'src/shared/enums'; import { CreateQuestionDto } from './dtos/create-question.dto'; import { UpdateQuestionDto } from './dtos/update-question.dto'; +import { + PaginatedQuestionPretestResponseDto, + QuestionPretestResponseDto, +} from './dtos/question-pretest-response.dto'; @Controller('question') @Injectable() @@ -73,6 +77,48 @@ export class QuestionController { ); } + @Get('/pretest') + @Roles(Role.STUDENT) + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all questions pretest', + type: PaginatedQuestionPretestResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findAllQuestionPretest( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return await this.questionService.findAllQuestionPretest( + request.user.id, + request.user.role, + { + page: query.page, + limit: query.limit, + search: query.search, + }, + ); + } + @Get(':id') @ApiResponse({ status: HttpStatus.OK, @@ -100,6 +146,58 @@ export class QuestionController { return new QuestionResponseDto(question); } + @Get('pretest/:pretestId') + @Roles(Role.STUDENT) + @Roles(Role.TEACHER) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all questions pretest with pretest id', + type: PaginatedQuestionPretestResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findQuestionPretestByPretestId( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + @Param( + 'pretestId', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + pretestId: string, + ): Promise { + const question = await this.questionService.findQuestionPretestByPretestId( + request.user.id, + request.user.role, + pretestId, + { + page: query.page, + limit: query.limit, + search: query.search, + }, + ); + return question; + } + @Get('exam/:examId') @ApiResponse({ status: HttpStatus.OK, diff --git a/src/question/question.entity.ts b/src/question/question.entity.ts index b7cdbb1..0dac85e 100644 --- a/src/question/question.entity.ts +++ b/src/question/question.entity.ts @@ -1,5 +1,6 @@ import { ExamAnswer } from 'src/exam-answer/exam-answer.entity'; import { Exam } from 'src/exam/exam.entity'; +import { Pretest } from 'src/pretest/pretest.entity'; import { QuestionOption } from 'src/question-option/question-option.entity'; import { QuestionType } from 'src/shared/enums'; import { @@ -21,14 +22,24 @@ export class Question { @ManyToOne(() => Exam, (exam) => exam.question, { onDelete: 'CASCADE', - nullable: false, + nullable: true, }) @JoinColumn({ name: 'exam_id' }) exam: Exam; - @Column({ name: 'exam_id' }) + @Column({ name: 'exam_id', nullable: true }) examId: string; + @ManyToOne(() => Pretest, (pretest) => pretest.question, { + onDelete: 'CASCADE', + nullable: true, + }) + @JoinColumn({ name: 'pretest_id' }) + pretest: Pretest; + + @Column({ name: 'pretest_id', nullable: true }) + pretestId: string; + @OneToMany( () => QuestionOption, (questionOption) => questionOption.question, @@ -57,13 +68,13 @@ export class Question { nullable: false, default: 1, }) - points: number; + points: number = 1; @Column({ nullable: false, default: 1, }) - orderIndex: number; + orderIndex: number = 1; @CreateDateColumn({ type: 'timestamp with time zone', diff --git a/src/question/question.module.ts b/src/question/question.module.ts index a68ce4f..0e362f1 100644 --- a/src/question/question.module.ts +++ b/src/question/question.module.ts @@ -6,9 +6,13 @@ import { questionProviders } from './question.providers'; import { QuestionController } from './question.controller'; import { QuestionService } from './question.service'; import { Exam } from 'src/exam/exam.entity'; +import { Pretest } from 'src/pretest/pretest.entity'; @Module({ - imports: [DatabaseModule, TypeOrmModule.forFeature([Question, Exam])], + imports: [ + DatabaseModule, + TypeOrmModule.forFeature([Question, Exam, Pretest]), + ], controllers: [QuestionController], providers: [...questionProviders, QuestionService], exports: [QuestionService], diff --git a/src/question/question.service.ts b/src/question/question.service.ts index dcc8a03..729be92 100644 --- a/src/question/question.service.ts +++ b/src/question/question.service.ts @@ -16,11 +16,15 @@ import { } from 'typeorm'; import { PaginatedQuestionResponseDto } from './dtos/question-response.dto'; import { createPagination } from 'src/shared/pagination'; -import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { CourseStatus, ExamStatus, QuestionType, Role } from 'src/shared/enums'; -import { CreateQuestionDto } from './dtos/create-question.dto'; +import { + CreateQuestionDto, + CreateQuestionPretestDto, +} from './dtos/create-question.dto'; import { UpdateQuestionDto } from './dtos/update-question.dto'; import { Exam } from 'src/exam/exam.entity'; +import { Pretest } from 'src/pretest/pretest.entity'; +import { PaginatedQuestionPretestResponseDto } from './dtos/question-pretest-response.dto'; @Injectable() export class QuestionService { constructor( @@ -28,6 +32,8 @@ export class QuestionService { private readonly questionRepository: Repository, @Inject('ExamRepository') private readonly examRepository: Repository, + @Inject('PretestRepository') + private readonly pretestRepository: Repository, ) {} async findAll( @@ -386,6 +392,162 @@ export class QuestionService { }; } + async findAllQuestionPretest( + userId: string, + role: Role, + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.questionRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateConditionForPretest( + userId, + role, + search, + ); + const question = await find({ + order: { + orderIndex: 'ASC', + }, + where: whereCondition, + relations: ['pretest', 'pretest.user'], + select: { + pretest: this.selectPopulatePretest(), + }, + }).run(); + + return question; + } + + async findQuestionPretestByPretestId( + userId: string, + role: Role, + pretestId: string, + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.questionRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateConditionForPretest( + userId, + role, + search, + ); + whereCondition['pretest'] = { id: pretestId }; + + const pretest = await this.pretestRepository.findOne({ + where: { id: pretestId }, + }); + + if (!pretest) { + throw new NotFoundException('Pretest not found.'); + } + + const question = await find({ + order: { + orderIndex: 'ASC', + }, + where: whereCondition, + relations: ['pretest', 'pretest.user'], + select: { + pretest: this.selectPopulatePretest(), + }, + }).run(); + return question; + } + + private validateAndCreateConditionForPretest( + userId: string, + role: Role, + search: string, + ): FindOptionsWhere { + const baseSearch = search ? { question: ILike(`%${search}%`) } : {}; + + if (role === Role.STUDENT) { + return { + ...baseSearch, + pretest: { + user: { + id: userId, + }, + }, + }; + } + + if (role === Role.ADMIN) { + return { ...baseSearch }; + } + + return { + ...baseSearch, + pretest: { + user: { + id: userId, + }, + }, + }; + } + + async createQuestionForPretest( + createQuestionPretestDto: CreateQuestionPretestDto, + ): Promise { + const pretest = await this.pretestRepository.findOne({ + where: { id: createQuestionPretestDto.pretestId }, + }); + + if (!pretest) throw new NotFoundException('Not found pretest'); + + const question = this.questionRepository.create({ + ...createQuestionPretestDto, + pretest, + }); + + if (!question) + throw new BadRequestException('Cannot create pretest question'); + + await this.questionRepository.save(question); + return question; + } + + private selectPopulatePretest(): FindOptionsSelect { + return { + id: true, + title: true, + description: true, + timeLimit: true, + passingScore: true, + maxAttempts: true, + user: { + id: true, + username: true, + fullname: true, + role: true, + email: true, + profileKey: true, + }, + }; + } + private checkPermission( userId: string, role: Role, diff --git a/src/shared/configs/database.config.ts b/src/shared/configs/database.config.ts index 68004b2..f20ba7b 100644 --- a/src/shared/configs/database.config.ts +++ b/src/shared/configs/database.config.ts @@ -23,6 +23,7 @@ import { User } from 'src/user/user.entity'; import { UserReward } from 'src/user-reward/user-reward.entity'; import { DataSource, DataSourceOptions } from 'typeorm'; import { GLOBAL_CONFIG } from '../constants/global-config.constant'; +import { Pretest } from 'src/pretest/pretest.entity'; const configService = new ConfigService(); @@ -56,6 +57,7 @@ export const databaseConfig: DataSourceOptions = { ChatRoom, ChatMessage, Roadmap, + Pretest, ], }; diff --git a/src/shared/enums/question-type.enum.ts b/src/shared/enums/question-type.enum.ts index f657d20..b5ce356 100644 --- a/src/shared/enums/question-type.enum.ts +++ b/src/shared/enums/question-type.enum.ts @@ -2,4 +2,5 @@ export enum QuestionType { MULTIPLE_CHOICE = 'multiple_choice', TRUE_FALSE = 'true_false', ESSAY = 'essay', + PRETEST = 'pretest', } diff --git a/src/user-background/user-background.service.ts b/src/user-background/user-background.service.ts index 21e0df3..335e3f3 100644 --- a/src/user-background/user-background.service.ts +++ b/src/user-background/user-background.service.ts @@ -79,8 +79,9 @@ export class UserBackgroundService { occupationId: data.occupationId, }); - const savedUserBackground = - await this.userBackgroundRepository.save(userBackground); + const savedUserBackground = await this.userBackgroundRepository.save( + userBackground, + ); return savedUserBackground; } @@ -98,8 +99,9 @@ export class UserBackgroundService { }; this.userBackgroundRepository.merge(userBackground, updateData); - const savedUserBackground = - await this.userBackgroundRepository.save(userBackground); + const savedUserBackground = await this.userBackgroundRepository.save( + userBackground, + ); return savedUserBackground; } @@ -113,4 +115,18 @@ export class UserBackgroundService { return userBackground; } + + async findOneByUserId(userId: string): Promise { + const userBackground = this.userBackgroundRepository.findOne({ + where: { user: { id: userId } }, + order: { updatedAt: 'DESC' }, + relations: { + user: true, + topics: true, + occupation: true, + }, + }); + if (!userBackground) throw new NotFoundException('Not found this user'); + return userBackground; + } } diff --git a/src/user/user.entity.ts b/src/user/user.entity.ts index d011044..8b929cf 100644 --- a/src/user/user.entity.ts +++ b/src/user/user.entity.ts @@ -14,6 +14,7 @@ import { PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; +import { Pretest } from 'src/pretest/pretest.entity'; @Entity() export class User { @@ -76,7 +77,13 @@ export class User { }) enrollments: Enrollment[]; - @OneToMany(() => ExamAttempt, (examAttempt) => examAttempt.exam, { + @OneToMany(() => Pretest, (pretest) => pretest.user, { + cascade: true, + nullable: true, + }) + pretest: Pretest[]; + + @OneToMany(() => ExamAttempt, (examAttempt) => examAttempt.user, { cascade: true, nullable: true, }) From daded369a3c29b6c5120d65ca13feb2d46985690 Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Tue, 26 Nov 2024 17:39:03 +0700 Subject: [PATCH 116/155] feat: implemnt asr --- src/chapter/chapter.controller.ts | 44 +++++------- src/chapter/chapter.module.ts | 4 +- src/chapter/chapter.service.ts | 77 +++++++++++++++++---- src/chapter/dtos/transcribe-response.dto.ts | 23 ++++++ 4 files changed, 108 insertions(+), 40 deletions(-) diff --git a/src/chapter/chapter.controller.ts b/src/chapter/chapter.controller.ts index 0dbc095..7fbc606 100644 --- a/src/chapter/chapter.controller.ts +++ b/src/chapter/chapter.controller.ts @@ -66,14 +66,13 @@ export class ChapterController { type: String, description: 'Course id', }) - @ApiBearerAuth() + @Public() @ApiResponse({ status: HttpStatus.OK, description: 'Get chapter video', type: StreamableFile, }) async getVideo( - @Req() request: AuthenticatedRequest, @Param( 'id', new ParseUUIDPipe({ @@ -83,12 +82,7 @@ export class ChapterController { ) id: string, ): Promise { - const chapter = await this.chapterService.findOneWithOwnership( - request.user.id, - request.user.role, - { where: { id } }, - ); - + const chapter = await this.chapterService.findOne({ where: { id } }); const file = await this.fileService.get(Folder.CHAPTER_VIDEOS, chapter.videoKey); return new StreamableFile(file, { disposition: 'inline', @@ -244,7 +238,7 @@ export class ChapterController { @ApiResponse({ status: HttpStatus.OK, type: ChapterResponseDto, - description: 'Get a chapter by ID', + description: 'Get a chapter by ID with ownership', }) @ApiParam({ name: 'id', @@ -260,9 +254,21 @@ export class ChapterController { where: { id }, }); } - - - + @Get('transcribe/:id') + @ApiResponse({ + status: HttpStatus.OK, + type: ChapterResponseDto, + description: 'Transcribe a chapter', + }) + @ApiParam({ + name: 'id', + type: String, + description: 'Chapter ID', + }) + @ApiBearerAuth() + async transcribe(@Param('id', ParseUUIDPipe) id: string) { + return await this.chapterService.transcribeAudio(id); + } @Get(':id') @ApiResponse({ @@ -329,20 +335,6 @@ export class ChapterController { return this.chapterService.create(createChapterDto); } - @Post('transcribe/:id') - @ApiResponse({ - status: HttpStatus.OK, - type: ChapterResponseDto, - description: 'Update a chapter', - }) - @ApiParam({ - name: 'id', - type: String, - description: 'Chapter ID', - }) - async transcribe(@Param('id', ParseUUIDPipe) id: string) { - return await this.chapterService.transcribeAudio(id); - } @Patch(':id') diff --git a/src/chapter/chapter.module.ts b/src/chapter/chapter.module.ts index 12e7d23..708a18c 100644 --- a/src/chapter/chapter.module.ts +++ b/src/chapter/chapter.module.ts @@ -11,6 +11,7 @@ import { ChatRoomModule } from 'src/chat-room/chat-room.module'; import { EnrollmentModule } from 'src/enrollment/enrollment.module'; import { FileModule } from 'src/file/file.module'; import { HttpModule } from '@nestjs/axios'; +import { ConfigModule } from '@nestjs/config'; @Module({ imports: [ @@ -20,7 +21,8 @@ import { HttpModule } from '@nestjs/axios'; ChatRoomModule, EnrollmentModule, FileModule, - HttpModule + HttpModule, + ConfigModule ], controllers: [ChapterController], providers: [...chapterProviders, ChapterService], diff --git a/src/chapter/chapter.service.ts b/src/chapter/chapter.service.ts index 9c5af91..3d87115 100644 --- a/src/chapter/chapter.service.ts +++ b/src/chapter/chapter.service.ts @@ -2,11 +2,12 @@ import { BadRequestException, ForbiddenException, Injectable, + InternalServerErrorException, NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { createPagination } from 'src/shared/pagination'; -import { FindOneOptions, FindOptionsWhere, ILike, Not, Repository } from 'typeorm'; +import { FindOneOptions, FindOptionsWhere, ILike, In, Not, Repository } from 'typeorm'; import { Chapter } from './chapter.entity'; import { PaginatedChapterResponseDto } from './dtos/chapter-response.dto'; import { CreateChapterDto } from './dtos/create-chapter.dto'; @@ -17,6 +18,11 @@ import { ChatRoomStatus, ChatRoomType } from 'src/chat-room/enums'; import { EnrollmentService } from 'src/enrollment/enrollment.service'; import { ChatRoom } from 'src/chat-room/chat-room.entity'; import { EnrollmentStatus } from 'src/enrollment/enums/enrollment-status.enum'; +import { ConfigService } from '@nestjs/config'; +import { HttpService } from '@nestjs/axios'; +import { TranscribeResponseDto } from './dtos/transcribe-response.dto'; +import { firstValueFrom } from 'rxjs'; +import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; @Injectable() export class ChapterService { @@ -26,6 +32,8 @@ export class ChapterService { private readonly chapterRepository: Repository, private readonly chatRoomService: ChatRoomService, private readonly enrollmentService: EnrollmentService, + private readonly configService: ConfigService, + private readonly httpService: HttpService, ) { } async findAll({ page = 1, @@ -239,8 +247,39 @@ export class ChapterService { return result; } - async transcribeAudio(id: string) { - throw new Error('Method not implemented.'); + async transcribeAudio(id: string): Promise { + + try { + const transcriptionResponse = await firstValueFrom( + this.httpService.post( + `${this.configService.get(GLOBAL_CONFIG.AI_URL)}/asr-public`, + { + url: `http://localhost:${this.configService.get(GLOBAL_CONFIG.PORT)}/chapter/${id}/video`, + language: 'en' + }, + { + headers: { + 'Content-Type': 'application/json' + } + } + ) + ); + + if (!transcriptionResponse.data) { + throw new BadRequestException('Failed to get transcription'); + } + + return transcriptionResponse.data; + + } catch (error) { + if (error.response) { + throw new InternalServerErrorException( + error.response.data || 'Transcription service error' + ); + } + + throw new InternalServerErrorException('Error processing transcription request'); + } } private async validateOrderIndex( @@ -311,21 +350,33 @@ export class ChapterService { Role, () => FindOptionsWhere | FindOptionsWhere[] > = { - [Role.STUDENT]: () => ({ - ...baseCondition, - module: { - course: { - status: CourseStatus.PUBLISHED, - enrollments: { - user: { id: userId }, - status: Not(EnrollmentStatus.DROPPED) + [Role.STUDENT]: () => [ + { + ...baseCondition, + module: { + course: { + status: CourseStatus.PUBLISHED, + enrollments: { + user: { id: userId }, + status: Not(EnrollmentStatus.DROPPED) + } + } + }, + }, + { + ...baseCondition, + isPreview: true, + module: { + course: { + status: CourseStatus.PUBLISHED } } - } - }), + }, + ], [Role.TEACHER]: () => [ { ...baseCondition, + isPreview: true, module: { course: { status: CourseStatus.PUBLISHED diff --git a/src/chapter/dtos/transcribe-response.dto.ts b/src/chapter/dtos/transcribe-response.dto.ts index e69de29..365238b 100644 --- a/src/chapter/dtos/transcribe-response.dto.ts +++ b/src/chapter/dtos/transcribe-response.dto.ts @@ -0,0 +1,23 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsNotEmpty, IsString } from "class-validator"; + +export class TranscribeResponseDto { + + @IsNotEmpty() + @IsString() + @ApiProperty({ + description: 'file path', + type: String, + example: 'downloads/video.mp3', + }) + file_path: string; + + @IsNotEmpty() + @IsString() + @ApiProperty({ + description: 'transcription', + type: String, + example: ' popular interview question concerns the four core concepts in object-oriented programming.', + }) + transcription: string; +} \ No newline at end of file From c5ac63c2b2cbbcd0a9fb09f7ac8f9a97d5623ee4 Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Tue, 26 Nov 2024 17:51:07 +0700 Subject: [PATCH 117/155] feat: adjust return structure for student role to include preview option in chapter service --- src/chapter/chapter.service.ts | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/chapter/chapter.service.ts b/src/chapter/chapter.service.ts index 413afd2..113a1f9 100644 --- a/src/chapter/chapter.service.ts +++ b/src/chapter/chapter.service.ts @@ -17,7 +17,6 @@ import { ChatRoomStatus, ChatRoomType } from 'src/chat-room/enums'; import { EnrollmentService } from 'src/enrollment/enrollment.service'; import { ChatRoom } from 'src/chat-room/chat-room.entity'; import { EnrollmentStatus } from 'src/enrollment/enums/enrollment-status.enum'; - @Injectable() export class ChapterService { constructor( @@ -306,21 +305,33 @@ export class ChapterService { Role, () => FindOptionsWhere | FindOptionsWhere[] > = { - [Role.STUDENT]: () => ({ - ...baseCondition, - module: { - course: { - status: CourseStatus.PUBLISHED, - enrollments: { - user: { id: userId }, - status: Not(EnrollmentStatus.DROPPED) + [Role.STUDENT]: () => [ + { + ...baseCondition, + module: { + course: { + status: CourseStatus.PUBLISHED, + enrollments: { + user: { id: userId }, + status: Not(EnrollmentStatus.DROPPED) + } + } + }, + }, + { + ...baseCondition, + isPreview: true, + module: { + course: { + status: CourseStatus.PUBLISHED } } - } - }), + }, + ], [Role.TEACHER]: () => [ { ...baseCondition, + isPreview: true, module: { course: { status: CourseStatus.PUBLISHED From 7c11357b9572800f73d47134e4bcf1d04e9babf3 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Tue, 26 Nov 2024 18:06:18 +0700 Subject: [PATCH 118/155] feat: create api for search pretest in question-option --- .../question-option-pretest-response.dto.ts | 108 +++++++++++++ .../dtos/question-option-response.dto.ts | 6 +- .../question-option.controller.ts | 94 +++++++++++ .../question-option.service.ts | 146 +++++++++++++++++- 4 files changed, 350 insertions(+), 4 deletions(-) create mode 100644 src/question-option/dtos/question-option-pretest-response.dto.ts diff --git a/src/question-option/dtos/question-option-pretest-response.dto.ts b/src/question-option/dtos/question-option-pretest-response.dto.ts new file mode 100644 index 0000000..c60c44f --- /dev/null +++ b/src/question-option/dtos/question-option-pretest-response.dto.ts @@ -0,0 +1,108 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { QuestionOption } from '../question-option.entity'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { QuestionPretestResponseDto } from 'src/question/dtos/question-pretest-response.dto'; + +export class QuestionOptionPretestResponseDto { + @ApiProperty({ + description: 'Question option ID', + type: String, + example: '123e4567-e89b-12d3-a456-426614174000', + }) + id: string; + + @ApiProperty({ + description: 'Question Data', + type: QuestionPretestResponseDto, + example: { + points: 1, + orderIndex: 10, + id: 'e99e39a0-edf0-470c-b8f1-9fe8fddd0ef3', + examId: null, + pretestId: '168eb5b6-9ba9-4e6d-9880-551d4232129e', + question: + "What is the difference between 'int()', 'float()', and 'str()' functions in Python?", + type: 'pretest', + createdAt: '2024-11-26T10:12:01.884Z', + updatedAt: '2024-11-26T10:12:01.884Z', + pretest: { + timeLimit: 20, + id: '168eb5b6-9ba9-4e6d-9880-551d4232129e', + title: 'Biology', + description: 'This course is an introduction to biology', + passingScore: 3, + maxAttempts: 1, + user: { + id: 'a12e1e37-3504-4711-a389-09a1734d7b1c', + username: 'johndoe', + fullname: 'John Doe', + role: 'student', + email: 'johndoe@gmail.com', + profileKey: null, + }, + }, + }, + }) + question: QuestionPretestResponseDto; + + @ApiProperty({ + description: 'Question option text', + type: String, + example: 'A. Rock', + }) + optionText: string; + + @ApiProperty({ + description: 'Is this question option correct?', + type: Boolean, + example: false, + }) + isCorrect: boolean; + + @ApiProperty({ + description: 'ehy this question option correct or incorrect?', + type: String, + example: 'Rock is not biology.', + }) + explanation: string; + + @ApiProperty({ + description: 'Exam created date', + type: Date, + example: new Date(), + }) + createdAt: Date; + + @ApiProperty({ + description: 'Exam updated date', + type: Date, + example: new Date(), + }) + updatedAt: Date; + + constructor(questionOption: QuestionOption) { + this.id = questionOption.id; + this.question = questionOption.question; + this.optionText = questionOption.optionText; + this.isCorrect = questionOption.isCorrect; + this.explanation = questionOption.explanation; + this.createdAt = questionOption.createdAt; + this.updatedAt = questionOption.updatedAt; + } +} + +export class PaginatedQuestionOptionPretestResponseDto extends PaginatedResponse( + QuestionOptionPretestResponseDto, +) { + constructor( + questionOption: QuestionOption[], + total: number, + pageSize: number, + currentPage: number, + ) { + const questionOptionDtos = questionOption.map( + (questionOption) => new QuestionOptionPretestResponseDto(questionOption), + ); + super(questionOptionDtos, total, pageSize, currentPage); + } +} diff --git a/src/question-option/dtos/question-option-response.dto.ts b/src/question-option/dtos/question-option-response.dto.ts index 7b2df6c..3e742b9 100644 --- a/src/question-option/dtos/question-option-response.dto.ts +++ b/src/question-option/dtos/question-option-response.dto.ts @@ -1,7 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { QuestionOption } from '../question-option.entity'; import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; -import { Question } from 'src/question/question.entity'; +import { QuestionResponseDto } from 'src/question/dtos/question-response.dto'; export class QuestionOptionResponseDto { @ApiProperty({ @@ -13,7 +13,7 @@ export class QuestionOptionResponseDto { @ApiProperty({ description: 'Question Data', - type: Question, + type: QuestionResponseDto, example: { id: 'e20ffe51-2514-4f14-9bea-4bb28bb97fdd', courseModuleId: '7093a5ae-cc1d-4017-8445-cba7ea978b22', @@ -29,7 +29,7 @@ export class QuestionOptionResponseDto { }, }, }) - question: Question; + question: QuestionResponseDto; @ApiProperty({ description: 'Question option text', diff --git a/src/question-option/question-option.controller.ts b/src/question-option/question-option.controller.ts index e97e4e0..be398bb 100644 --- a/src/question-option/question-option.controller.ts +++ b/src/question-option/question-option.controller.ts @@ -25,6 +25,7 @@ import { Roles } from 'src/shared/decorators/role.decorator'; import { Role } from 'src/shared/enums'; import { CreateQuestionOptionDto } from './dtos/create-question-option.dto'; import { UpdateQuestionOptionDto } from './dtos/update-question-option.dto'; +import { PaginatedQuestionOptionPretestResponseDto } from './dtos/question-option-pretest-response.dto'; @Controller('question-option') @Injectable() @@ -73,6 +74,48 @@ export class QuestionOptionController { ); } + @Get('/pretest') + @Roles(Role.STUDENT) + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all question option pretest', + type: PaginatedQuestionOptionPretestResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findAllQuestionPretest( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return await this.questionOptionService.findAllQuestionOptionPretest( + request.user.id, + request.user.role, + { + page: query.page, + limit: query.limit, + search: query.search, + }, + ); + } + @Get(':id') @ApiResponse({ status: HttpStatus.OK, @@ -100,6 +143,57 @@ export class QuestionOptionController { return new QuestionOptionResponseDto(questionOption); } + @Get('pretest/:questionId') + @Roles(Role.STUDENT) + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all question options in pretest id', + type: PaginatedQuestionOptionPretestResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findQuestionOptionPretestByQuestionId( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + @Param( + 'questionId', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + questionId: string, + ): Promise { + return await this.questionOptionService.findQuestionOptionPretestByQuestionId( + request.user.id, + request.user.role, + questionId, + { + page: query.page, + limit: query.limit, + search: query.search, + }, + ); + } + @Get('question/:questionId') @ApiResponse({ status: HttpStatus.OK, diff --git a/src/question-option/question-option.service.ts b/src/question-option/question-option.service.ts index 514ed2c..5799ac2 100644 --- a/src/question-option/question-option.service.ts +++ b/src/question-option/question-option.service.ts @@ -21,6 +21,7 @@ import { CourseStatus, ExamStatus, QuestionType, Role } from 'src/shared/enums'; import { CreateQuestionOptionDto } from './dtos/create-question-option.dto'; import { Question } from 'src/question/question.entity'; import { UpdateQuestionOptionDto } from './dtos/update-question-option.dto'; +import { PaginatedQuestionOptionPretestResponseDto } from './dtos/question-option-pretest-response.dto'; @Injectable() export class QuestionOptionService { constructor( @@ -130,7 +131,10 @@ export class QuestionOptionService { role, search, ); - whereCondition['question'] = { id: questionId }; + whereCondition['question'] = { + id: questionId, + type: Not(QuestionType.PRETEST), + }; const question = await this.questionRepository.findOne({ where: { id: questionId }, @@ -341,4 +345,144 @@ export class QuestionOptionService { return false; } } + + async findAllQuestionOptionPretest( + userId: string, + role: Role, + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.questionOptionRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateConditionForPretest( + userId, + role, + search, + ); + const question = await find({ + where: whereCondition, + relations: ['question', 'question.pretest', 'question.pretest.user'], + select: { + question: this.selectPopulateQuestionForPretest(), + }, + }).run(); + + return question; + } + + async findQuestionOptionPretestByQuestionId( + userId: string, + role: Role, + questionId: string, + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.questionOptionRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateConditionForPretest( + userId, + role, + search, + ); + whereCondition['question'] = { id: questionId, type: QuestionType.PRETEST }; + + const question = await this.questionRepository.findOne({ + where: { id: questionId }, + }); + + if (!question) { + throw new NotFoundException('Question not found.'); + } + + const questionOption = await find({ + where: whereCondition, + relations: ['question', 'question.pretest', 'question.pretest.user'], + select: { + question: this.selectPopulateQuestionForPretest(), + }, + }).run(); + return questionOption; + } + + private validateAndCreateConditionForPretest( + userId: string, + role: Role, + search: string, + ): FindOptionsWhere { + const baseSearch = search ? { optionText: ILike(`%${search}%`) } : {}; + + if (role === Role.STUDENT) { + return { + ...baseSearch, + question: { + pretest: { + user: { + id: userId, + }, + }, + }, + }; + } + + if (role === Role.ADMIN) { + return { ...baseSearch }; + } + + return { + ...baseSearch, + question: { + pretest: { + user: { + id: userId, + }, + }, + }, + }; + } + + private selectPopulateQuestionForPretest(): FindOptionsSelect { + return { + id: true, + question: true, + type: true, + points: true, + orderIndex: true, + pretest: { + id: true, + timeLimit: true, + title: true, + description: true, + passingScore: true, + maxAttempts: true, + user: { + id: true, + username: true, + fullname: true, + role: true, + email: true, + profileKey: true, + }, + }, + }; + } } From 8d36d76aec9395c2c1a457207ea15ba0b00fee6b Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Tue, 26 Nov 2024 18:09:27 +0700 Subject: [PATCH 119/155] feat:create question-option for pretest --- src/pretest/pretest.service.ts | 2 +- .../question-option.service.ts | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/pretest/pretest.service.ts b/src/pretest/pretest.service.ts index 7f7b89c..5e4efbd 100644 --- a/src/pretest/pretest.service.ts +++ b/src/pretest/pretest.service.ts @@ -278,7 +278,7 @@ export class PretestService { isCorrect: key === data.answer, explanation: '', }; - return this.questionOptionService.createQuestionOption( + return this.questionOptionService.createQuestionOptionPretest( createQuestionOptionDto, ); }), diff --git a/src/question-option/question-option.service.ts b/src/question-option/question-option.service.ts index 5799ac2..9573f92 100644 --- a/src/question-option/question-option.service.ts +++ b/src/question-option/question-option.service.ts @@ -256,6 +256,28 @@ export class QuestionOptionService { return questionOption; } + async createQuestionOptionPretest( + createQuestionOptionDto: CreateQuestionOptionDto, + ): Promise { + const question = await this.questionRepository.findOne({ + where: { id: createQuestionOptionDto.questionId }, + select: this.selectPopulateQuestion(), + relations: ['pretest', 'pretest.user'], + }); + if (!question) { + throw new NotFoundException('Question option not found.'); + } + const questionOption = await this.questionOptionRepository.create({ + ...createQuestionOptionDto, + question, + }); + if (!questionOption) { + throw new BadRequestException('Question option not create.'); + } + await this.questionOptionRepository.save(questionOption); + return questionOption; + } + async updateQuestionOption( userId: string, role: Role, From ef7d983702ce78db65182f4442a6b7df094b0088 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Tue, 26 Nov 2024 18:57:46 +0700 Subject: [PATCH 120/155] fix: correct environment variable name for JWT refresh expiration in workflow --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fbe8cd4..b883793 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,7 +30,7 @@ jobs: echo "JWT_ACCESS_SECRET=${{ secrets.JWT_ACCESS_SECRET }}" >> .env echo "JWT_REFRESH_SECRET=${{ secrets.JWT_REFRESH_SECRET }}" >> .env echo "JWT_ACCESS_EXPIRATION=${{ secrets.JWT_ACCESS_EXPIRATION }}" >> .env - echo "AI_HOST=${{ secrets.JWT_REFRESH_EXPIRATION }}" >> .env + echo "JWT_REFRESH_EXPIRATION=${{ secrets.JWT_REFRESH_EXPIRATION }}" >> .env echo "AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }}" >> .env echo "AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }}" >> .env echo "AWS_REGION=${{ secrets.AWS_REGION }}" >> .env From e8faac92436bbf8906eff3e6eef499af8d66d6c5 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Tue, 26 Nov 2024 21:36:50 +0700 Subject: [PATCH 121/155] feat: create pretest in exam-attempt --- .../dtos/create-exam-attempt.dto.ts | 30 ++ .../dtos/exam-attempt-pretest.dto.ts | 100 +++++++ .../dtos/update-exam-attempt.dto.ts | 9 +- src/exam-attempt/exam-attempt.controller.ts | 186 +++++++++++- src/exam-attempt/exam-attempt.module.ts | 3 +- src/exam-attempt/exam-attempt.service.dto.ts | 278 +++++++++++++++++- src/pretest/pretest.controller.ts | 5 - .../question-option-pretest-response.dto.ts | 2 - .../dtos/question-pretest-response.dto.ts | 2 - 9 files changed, 594 insertions(+), 21 deletions(-) create mode 100644 src/exam-attempt/dtos/exam-attempt-pretest.dto.ts diff --git a/src/exam-attempt/dtos/create-exam-attempt.dto.ts b/src/exam-attempt/dtos/create-exam-attempt.dto.ts index b1987ff..1851e5c 100644 --- a/src/exam-attempt/dtos/create-exam-attempt.dto.ts +++ b/src/exam-attempt/dtos/create-exam-attempt.dto.ts @@ -31,3 +31,33 @@ export class CreateExamAttemptDto { }) status: ExamAttemptStatus; } + +export class CreateExamAttemptPretestDto { + @IsNotEmpty() + @ApiProperty({ + description: 'Pretest ID', + type: String, + example: '8d4887aa-28e7-4d0e-844c-28a8ccead003', + }) + pretestId: string; + + @IsOptional() + @Min(0) + @IsInt() + @ApiProperty({ + description: 'Score', + type: Number, + example: 0, + }) + score?: number; + + @IsNotEmpty() + @IsEnum(ExamAttemptStatus) + @ApiProperty({ + description: 'Exam attempt status', + type: String, + example: ExamAttemptStatus.IN_PROGRESS, + enum: ExamAttemptStatus, + }) + status: ExamAttemptStatus; +} diff --git a/src/exam-attempt/dtos/exam-attempt-pretest.dto.ts b/src/exam-attempt/dtos/exam-attempt-pretest.dto.ts new file mode 100644 index 0000000..a44661c --- /dev/null +++ b/src/exam-attempt/dtos/exam-attempt-pretest.dto.ts @@ -0,0 +1,100 @@ +import { ExamAttemptStatus } from 'src/shared/enums'; +import { ApiProperty } from '@nestjs/swagger'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { UserResponseDto } from 'src/user/dtos/user-response.dto'; +import { ExamAttempt } from '../exam-attempt.entity'; +import { PretestResponseDto } from 'src/pretest/dtos/pretest-response.dto'; + +export class ExamAttemptPretestResponseDto { + @ApiProperty({ + description: 'Exam Attempy ID', + type: String, + example: '123e4567-e89b-12d3-a456-426614174000', + }) + id: string; + + @ApiProperty({ + description: 'Pretest Data', + type: PretestResponseDto, + example: { + id: '123e4567-e89b-12d3-a456-426614174000', + title: 'Exam title', + description: 'Exam description', + timeLimit: 20, + passingScore: 3, + maxAttempts: 1, + createdAt: '2024-11-26T11:10:00.257Z', + updatedAt: '2024-11-26T11:10:00.257Z', + }, + }) + pretest: PretestResponseDto; + + @ApiProperty({ + description: 'User Data', + type: String, + example: { + id: '389d1065-b898-4249-9d3d-e17e100336a7', + username: 'johndoe', + fullname: 'John Doe', + role: 'student', + email: 'johndoe@gmail.com', + profileKey: null, + }, + }) + user: UserResponseDto; + + @ApiProperty({ + description: 'Score', + type: String, + example: 0, + }) + score: number; + + @ApiProperty({ + description: 'Exam attempt status', + type: String, + example: ExamAttemptStatus.IN_PROGRESS, + enum: ExamAttemptStatus, + }) + status: ExamAttemptStatus; + + @ApiProperty({ + description: 'Exam attempt start at', + type: Date, + example: new Date(), + }) + startedAt: Date; + + @ApiProperty({ + description: 'Exam attempt submit at', + type: Date, + example: new Date(), + }) + submittedAt: Date; + + constructor(examAttempt: ExamAttempt) { + this.id = examAttempt.id; + this.pretest = examAttempt.pretest; + this.user = examAttempt.user; + this.score = examAttempt.score; + this.status = examAttempt.status; + this.startedAt = examAttempt.startedAt; + this.submittedAt = examAttempt.submittedAt; + } +} + +export class PaginatedExamAttemptPretestResponseDto extends PaginatedResponse( + ExamAttemptPretestResponseDto, +) { + constructor( + examAttempt: ExamAttempt[], + total: number, + pageSize: number, + currentPage: number, + ) { + const examAttemptDtos = examAttempt.map( + (examAttempt) => new ExamAttemptPretestResponseDto(examAttempt), + ); + super(examAttemptDtos, total, pageSize, currentPage); + } +} diff --git a/src/exam-attempt/dtos/update-exam-attempt.dto.ts b/src/exam-attempt/dtos/update-exam-attempt.dto.ts index c6a0cf3..424bbdb 100644 --- a/src/exam-attempt/dtos/update-exam-attempt.dto.ts +++ b/src/exam-attempt/dtos/update-exam-attempt.dto.ts @@ -1,6 +1,13 @@ import { OmitType, PartialType } from '@nestjs/swagger'; -import { CreateExamAttemptDto } from './create-exam-attempt.dto'; +import { + CreateExamAttemptDto, + CreateExamAttemptPretestDto, +} from './create-exam-attempt.dto'; export class UpdateExamAttemptDto extends PartialType( OmitType(CreateExamAttemptDto, ['examId'] as const), ) {} + +export class UpdateExamAttemptPretestDto extends PartialType( + OmitType(CreateExamAttemptPretestDto, ['pretestId'] as const), +) {} diff --git a/src/exam-attempt/exam-attempt.controller.ts b/src/exam-attempt/exam-attempt.controller.ts index 55e2c2f..6dc1b74 100644 --- a/src/exam-attempt/exam-attempt.controller.ts +++ b/src/exam-attempt/exam-attempt.controller.ts @@ -23,9 +23,19 @@ import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto' import { ExamAttemptService } from './exam-attempt.service.dto'; import { Roles } from 'src/shared/decorators/role.decorator'; import { Role } from 'src/shared/enums'; -import { CreateExamAttemptDto } from './dtos/create-exam-attempt.dto'; -import { UpdateExamAttemptDto } from './dtos/update-exam-attempt.dto'; +import { + CreateExamAttemptDto, + CreateExamAttemptPretestDto, +} from './dtos/create-exam-attempt.dto'; +import { + UpdateExamAttemptDto, + UpdateExamAttemptPretestDto, +} from './dtos/update-exam-attempt.dto'; import { Exam } from 'src/exam/exam.entity'; +import { + ExamAttemptPretestResponseDto, + PaginatedExamAttemptPretestResponseDto, +} from './dtos/exam-attempt-pretest.dto'; @Controller('exam-attempt') @ApiTags('ExamAttempt') @@ -74,6 +84,46 @@ export class ExamAttemptController { ); } + @Get('/pretest') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all exam-attempt pretest', + type: PaginatedExamAttemptPretestResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findAllExamAttemptPretest( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return await this.examAttemptService.findAllExamAttemptPretest( + request.user.id, + request.user.role, + { + page: query.page, + limit: query.limit, + search: query.search, + }, + ); + } + @Get(':id') @ApiResponse({ status: HttpStatus.OK, @@ -101,6 +151,33 @@ export class ExamAttemptController { return new ExamAttemptResponseDto(exam); } + @Get('/pretest/:id') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns an exam-attempt pretest', + type: ExamAttemptPretestResponseDto, + }) + async findOneExamAttemptPretest( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const exam = await this.examAttemptService.findOneExamAttemptPrestest( + request.user.id, + request.user.role, + { + where: { id }, + }, + ); + return new ExamAttemptPretestResponseDto(exam); + } + @Post() @Roles(Role.STUDENT) @ApiResponse({ @@ -120,11 +197,58 @@ export class ExamAttemptController { return new ExamAttemptResponseDto(exam); } + @Post('/pretest') + @Roles(Role.STUDENT) + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Create an exam-attempt pretest', + type: ExamAttemptPretestResponseDto, + }) + @HttpCode(HttpStatus.CREATED) + async createExamAttemptPretest( + @Req() request: AuthenticatedRequest, + @Body() createExamAttemptPretestDto: CreateExamAttemptPretestDto, + ): Promise { + const exam = await this.examAttemptService.createExamAttemptPretest( + request.user.id, + createExamAttemptPretestDto, + ); + return new ExamAttemptPretestResponseDto(exam); + } + @Patch(':id') @Roles(Role.STUDENT) @ApiResponse({ status: HttpStatus.OK, description: 'Update an exam-attempt', + type: ExamAttemptPretestResponseDto, + }) + async updateExamAttemptPretest( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + @Body() updateExamAttemptPressDto: UpdateExamAttemptPretestDto, + ): Promise { + const exam = await this.examAttemptService.updateExamAttempt( + request.user.id, + request.user.role, + id, + updateExamAttemptPressDto, + ); + return new ExamAttemptPretestResponseDto(exam); + } + + @Patch('/pretest/:id') + @Roles(Role.STUDENT) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Update an exam-attempt ptetest', type: ExamAttemptResponseDto, }) async updateExamAttempt( @@ -138,14 +262,14 @@ export class ExamAttemptController { ) id: string, @Body() updateExamAttemptDto: UpdateExamAttemptDto, - ): Promise { - const exam = await this.examAttemptService.updateExamAttempt( + ): Promise { + const exam = await this.examAttemptService.updateExamAttemptPretest( request.user.id, request.user.role, id, updateExamAttemptDto, ); - return new ExamAttemptResponseDto(exam); + return new ExamAttemptPretestResponseDto(exam); } @Patch('/submit/:id') @@ -174,6 +298,32 @@ export class ExamAttemptController { return new ExamAttemptResponseDto(exam); } + @Patch('/pretest/submit/:id') + @Roles(Role.STUDENT) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Update an exam-attempt pretest', + type: ExamAttemptPretestResponseDto, + }) + async submitExamAttemptPretest( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const exam = await this.examAttemptService.submittedExamPretest( + request.user.id, + request.user.role, + id, + ); + return new ExamAttemptPretestResponseDto(exam); + } + @Delete(':id') @Roles(Role.TEACHER) @Roles(Role.ADMIN) @@ -200,4 +350,30 @@ export class ExamAttemptController { ); return new ExamAttemptResponseDto(examAttempt); } + + @Delete('/pretest/:id') + @Roles(Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Delete an exam', + type: ExamAttemptPretestResponseDto, + }) + async deleteExamAttemptPretest( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ): Promise { + const examAttempt = await this.examAttemptService.deleteExamAttemptPretest( + request.user.id, + request.user.role, + id, + ); + return new ExamAttemptPretestResponseDto(examAttempt); + } } diff --git a/src/exam-attempt/exam-attempt.module.ts b/src/exam-attempt/exam-attempt.module.ts index de0f2eb..2c33fb3 100644 --- a/src/exam-attempt/exam-attempt.module.ts +++ b/src/exam-attempt/exam-attempt.module.ts @@ -7,11 +7,12 @@ import { examAttemptProviders } from './exam-attempt.providers'; import { ExamAttemptService } from './exam-attempt.service.dto'; import { Exam } from 'src/exam/exam.entity'; import { User } from 'src/user/user.entity'; +import { Pretest } from 'src/pretest/pretest.entity'; @Module({ imports: [ DatabaseModule, - TypeOrmModule.forFeature([ExamAttempt, Exam, User]), + TypeOrmModule.forFeature([ExamAttempt, Exam, User, Pretest]), ], controllers: [ExamAttemptController], providers: [...examAttemptProviders, ExamAttemptService], diff --git a/src/exam-attempt/exam-attempt.service.dto.ts b/src/exam-attempt/exam-attempt.service.dto.ts index f3c2003..20e362e 100644 --- a/src/exam-attempt/exam-attempt.service.dto.ts +++ b/src/exam-attempt/exam-attempt.service.dto.ts @@ -23,10 +23,18 @@ import { ExamStatus, Role, } from 'src/shared/enums'; -import { CreateExamAttemptDto } from './dtos/create-exam-attempt.dto'; +import { + CreateExamAttemptDto, + CreateExamAttemptPretestDto, +} from './dtos/create-exam-attempt.dto'; import { Exam } from 'src/exam/exam.entity'; -import { UpdateExamAttemptDto } from './dtos/update-exam-attempt.dto'; +import { + UpdateExamAttemptDto, + UpdateExamAttemptPretestDto, +} from './dtos/update-exam-attempt.dto'; import { User } from 'src/user/user.entity'; +import { Pretest } from 'src/pretest/pretest.entity'; +import { PaginatedExamAttemptPretestResponseDto } from './dtos/exam-attempt-pretest.dto'; @Injectable() export class ExamAttemptService { @@ -37,6 +45,8 @@ export class ExamAttemptService { private readonly examRepository: Repository, @Inject('UserRepository') private readonly userRepository: Repository, + @Inject('PretestRepository') + private readonly pretestRepository: Repository, ) {} async findAll( @@ -191,8 +201,10 @@ export class ExamAttemptService { if (user.role != Role.STUDENT) throw new ForbiddenException('User is not student.'); if ( - (await this.countExamAttempts(createExamAttemptDto.examId, userId)) >= - exam.maxAttempts + (await this.countExamAttemptsWithExamId( + createExamAttemptDto.examId, + userId, + )) >= exam.maxAttempts ) throw new ForbiddenException( "Can't create exam-attempt more than max attempt", @@ -216,6 +228,8 @@ export class ExamAttemptService { const examAttemptInData = await this.findOne(userId, role, { where: { id }, }); + if (examAttemptInData.submittedAt) + throw new ForbiddenException('Already submitted'); if ( examAttemptInData.status != ExamAttemptStatus.IN_PROGRESS && updateExamAttemptDto.status == ExamAttemptStatus.IN_PROGRESS @@ -272,7 +286,10 @@ export class ExamAttemptService { return updatedExamAttempt; } - async countExamAttempts(examId: string, userId: string): Promise { + async countExamAttemptsWithExamId( + examId: string, + userId: string, + ): Promise { const count = await this.examAttemptRepository .createQueryBuilder('examAttempt') .where('examAttempt.examId = :examId AND examAttempt.userId = :userId', { @@ -307,4 +324,255 @@ export class ExamAttemptService { profileKey: true, }; } + + async findAllExamAttemptPretest( + userId: string, + role: Role, + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.examAttemptRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateConditionForPretest( + userId, + role, + search, + ); + const exam = await find({ + where: whereCondition, + relations: ['pretest', 'user'], + select: { + user: this.selectPopulateUser(), + pretest: this.selectPopulatePretest(), + }, + }).run(); + + return exam; + } + + async findOneExamAttemptPrestest( + userId: string, + role: Role, + options: FindOneOptions = {}, + ): Promise { + const whereCondition = this.validateAndCreateConditionForPretest( + userId, + role, + '', + ); + + const where = Array.isArray(whereCondition) + ? [ + { ...whereCondition[0], ...options.where }, + { ...whereCondition[1], ...options.where }, + ] + : { ...whereCondition, ...options.where }; + + const exam = await this.examAttemptRepository.findOne({ + ...options, + where, + relations: ['pretest', 'user'], + select: { + user: this.selectPopulateUser(), + pretest: this.selectPopulatePretest(), + }, + }); + + if (!exam) { + throw new NotFoundException('Exam not found'); + } + + return exam; + } + + private validateAndCreateConditionForPretest( + userId: string, + role: Role, + search: string, + ): FindOptionsWhere | FindOptionsWhere[] { + const baseSearch = search ? { id: ILike(`%${search}%`) } : {}; + + if (role === Role.ADMIN) { + return { ...baseSearch }; + } + + if (role === Role.STUDENT) { + return { + ...baseSearch, + pretest: { + user: { + id: userId, + }, + }, + }; + } + + return { + ...baseSearch, + pretest: { + user: { + id: userId, + }, + }, + }; + } + + async createExamAttemptPretest( + userId: string, + createExamAttemptPretestDto: CreateExamAttemptPretestDto, + ): Promise { + const pretest = await this.pretestRepository.findOne({ + where: { id: createExamAttemptPretestDto.pretestId }, + select: this.selectPopulatePretest(), + }); + if (!pretest) { + throw new NotFoundException('Pretest not found.'); + } + const user = await this.userRepository.findOne({ + where: { id: userId }, + select: this.selectPopulateUser(), + }); + if (!user) { + throw new NotFoundException('User not found.'); + } + if (user.role != Role.STUDENT) + throw new ForbiddenException('User is not student.'); + if ( + (await this.countExamAttemptsWithPretestId( + createExamAttemptPretestDto.pretestId, + userId, + )) >= pretest.maxAttempts + ) + throw new ForbiddenException( + "Can't create exam-attempt more than max attempt", + ); + const examAttempt = await this.examAttemptRepository.create({ + ...createExamAttemptPretestDto, + pretest, + user, + }); + if (!examAttempt) throw new NotFoundException("Can't create exam-attempt"); + await this.examAttemptRepository.save(examAttempt); + return examAttempt; + } + + async updateExamAttemptPretest( + userId: string, + role: Role, + id: string, + updateExamAttemptPretestDto: UpdateExamAttemptPretestDto, + ): Promise { + const examAttemptInData = await this.findOneExamAttemptPrestest( + userId, + role, + { + where: { id }, + }, + ); + if (examAttemptInData.submittedAt) + throw new ForbiddenException('Already submitted'); + if ( + examAttemptInData.status != ExamAttemptStatus.IN_PROGRESS && + updateExamAttemptPretestDto.status == ExamAttemptStatus.IN_PROGRESS + ) { + throw new ForbiddenException("Can't change status to in progress"); + } + const examAttempt = await this.examAttemptRepository.update( + id, + updateExamAttemptPretestDto, + ); + if (!examAttempt) + throw new BadRequestException("Can't update exam-attempt"); + return await this.examAttemptRepository.findOne({ + where: { id }, + relations: ['pretest', 'user'], + select: { + user: this.selectPopulateUser(), + pretest: this.selectPopulatePretest(), + }, + }); + } + + async deleteExamAttemptPretest( + userId: string, + role: Role, + id: string, + ): Promise { + try { + const examAttempt = await this.findOneExamAttemptPrestest(userId, role, { + where: { id }, + }); + return await this.examAttemptRepository.remove(examAttempt); + } catch (error) { + if (error instanceof Error) + throw new NotFoundException('Exam-attempt not found'); + } + } + + async submittedExamPretest( + userId: string, + role: Role, + id: string, + ): Promise { + const examAttemptInData = await this.findOneExamAttemptPrestest( + userId, + role, + { + where: { id }, + }, + ); + + examAttemptInData.submittedAt = new Date(); + + await this.examAttemptRepository.update(id, examAttemptInData); + + const updatedExamAttempt = await this.findOneExamAttemptPrestest( + userId, + role, + { + where: { id }, + }, + ); + + return updatedExamAttempt; + } + + async countExamAttemptsWithPretestId( + pretestId: string, + userId: string, + ): Promise { + const count = await this.examAttemptRepository + .createQueryBuilder('examAttempt') + .where( + 'examAttempt.pretestId = :pretestId AND examAttempt.userId = :userId', + { + pretestId, + userId, + }, + ) + .getCount(); + + return count; + } + + private selectPopulatePretest(): FindOptionsSelect { + return { + id: true, + title: true, + description: true, + timeLimit: true, + passingScore: true, + maxAttempts: true, + }; + } } diff --git a/src/pretest/pretest.controller.ts b/src/pretest/pretest.controller.ts index 5114e7b..cf11247 100644 --- a/src/pretest/pretest.controller.ts +++ b/src/pretest/pretest.controller.ts @@ -174,9 +174,4 @@ export class PretestController { ); return new PretestResponseDto(pretest); } - - @Get('pretest/test') - async test(@Req() request: AuthenticatedRequest): Promise { - this.pretestService.fetchData(request.user.id); - } } diff --git a/src/question-option/dtos/question-option-pretest-response.dto.ts b/src/question-option/dtos/question-option-pretest-response.dto.ts index c60c44f..15b31e4 100644 --- a/src/question-option/dtos/question-option-pretest-response.dto.ts +++ b/src/question-option/dtos/question-option-pretest-response.dto.ts @@ -23,8 +23,6 @@ export class QuestionOptionPretestResponseDto { question: "What is the difference between 'int()', 'float()', and 'str()' functions in Python?", type: 'pretest', - createdAt: '2024-11-26T10:12:01.884Z', - updatedAt: '2024-11-26T10:12:01.884Z', pretest: { timeLimit: 20, id: '168eb5b6-9ba9-4e6d-9880-551d4232129e', diff --git a/src/question/dtos/question-pretest-response.dto.ts b/src/question/dtos/question-pretest-response.dto.ts index 8cef109..6ae6aa2 100644 --- a/src/question/dtos/question-pretest-response.dto.ts +++ b/src/question/dtos/question-pretest-response.dto.ts @@ -31,8 +31,6 @@ export class QuestionPretestResponseDto { timeLimit: 20, passingScore: 3, maxAttempts: 1, - createdAt: '2024-11-26T10:06:39.408Z', - updatedAt: '2024-11-26T10:06:39.408Z', }, }) pretest: Pretest; From fb5a7c7e0cf070bb8fd4b68b2b731a95da7d96e0 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Tue, 26 Nov 2024 21:53:25 +0700 Subject: [PATCH 122/155] feat: add topics in request body --- src/pretest/pretest.service.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/pretest/pretest.service.ts b/src/pretest/pretest.service.ts index 5e4efbd..ba767aa 100644 --- a/src/pretest/pretest.service.ts +++ b/src/pretest/pretest.service.ts @@ -228,17 +228,17 @@ export class PretestService { }, createdAt: new Date(), updatedAt: new Date(), - // topics: - // userBackground.topics.length > 0 - // ? userBackground.topics.map((topic) => ({ - // id: topic.id, - // title: topic.title, - // description: topic.description, - // level: topic.level, - // createdAt: topic.createdAt, - // updatedAt: topic.updatedAt, - // })) - // : [], + topics: + userBackground.topics.length > 0 + ? userBackground.topics.map((topic) => ({ + id: topic.id, + title: topic.title, + description: topic.description, + level: topic.level, + createdAt: topic.createdAt, + updatedAt: topic.updatedAt, + })) + : [], }; const response = await this.httpService.axiosRef.post( `${this.configService.get( From 9ae9d22dcd9cca6eb11dc78d5d69dbac4c8ae75f Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Tue, 26 Nov 2024 22:12:07 +0700 Subject: [PATCH 123/155] feat: add parametor in requestbody --- src/pretest/pretest.service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pretest/pretest.service.ts b/src/pretest/pretest.service.ts index ba767aa..2665856 100644 --- a/src/pretest/pretest.service.ts +++ b/src/pretest/pretest.service.ts @@ -203,13 +203,13 @@ export class PretestService { }; } - async fetchData(userId: string): Promise { + async fetchData(userId: string, pretestId: string): Promise { const userBackground = await this.userBackgroundService.findOneByUserId( userId, ); try { const requestBody = { - id: '1', + id: pretestId, user: { id: userBackground.user.id, email: userBackground.user.email, @@ -256,7 +256,7 @@ export class PretestService { pretestId: string, userId: string, ): Promise { - const fetchData = await this.fetchData(userId); + const fetchData = await this.fetchData(userId, pretestId); let orderIndex = 1; await Promise.all( fetchData.data.map(async (data) => { From 7ade2b7d29f983b87912b5096432c3f054131cf9 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Tue, 26 Nov 2024 22:22:27 +0700 Subject: [PATCH 124/155] feat: add ai in path --- src/pretest/pretest.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pretest/pretest.service.ts b/src/pretest/pretest.service.ts index 2665856..cf2e449 100644 --- a/src/pretest/pretest.service.ts +++ b/src/pretest/pretest.service.ts @@ -243,7 +243,7 @@ export class PretestService { const response = await this.httpService.axiosRef.post( `${this.configService.get( GLOBAL_CONFIG.AI_URL, - )}/generate-pretest/`, + )}/ai/generate-pretest/`, requestBody, ); return { data: response.data }; From 192b0ba089d8f300ffd2c46214213895e74249db Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Tue, 26 Nov 2024 22:24:08 +0700 Subject: [PATCH 125/155] feat: replace AuthenticatedRequest with Public decorator in getVideo method and simplify chapter retrieval logic --- src/chapter/chapter.controller.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/chapter/chapter.controller.ts b/src/chapter/chapter.controller.ts index 06b51e8..ba94715 100644 --- a/src/chapter/chapter.controller.ts +++ b/src/chapter/chapter.controller.ts @@ -66,14 +66,13 @@ export class ChapterController { type: String, description: 'Course id', }) - @ApiBearerAuth() + @Public() @ApiResponse({ status: HttpStatus.OK, description: 'Get chapter video', type: StreamableFile, }) async getVideo( - @Req() request: AuthenticatedRequest, @Param( 'id', new ParseUUIDPipe({ @@ -83,11 +82,9 @@ export class ChapterController { ) id: string, ): Promise { - const chapter = await this.chapterService.findOneWithOwnership( - request.user.id, - request.user.role, - { where: { id } }, - ); + const chapter = await this.chapterService.findOne({ + where: { id }, + }); const file = await this.fileService.get(Folder.CHAPTER_VIDEOS, chapter.videoKey); return new StreamableFile(file, { From fda1bfb42ec23e4b1275f95ea310328a94bd6358 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Wed, 27 Nov 2024 12:45:11 +0700 Subject: [PATCH 126/155] fix: update volume path in docker-compose.yml to use environment variable --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 72ca334..03bbcbb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ services: POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: ${DB_DATABASE} volumes: - - ./postgres-data:/var/lib/postgresql/data + - ${VOLUMES_PATH}:/var/lib/postgresql/data healthcheck: test: ['CMD-SHELL', 'pg_isready -U ${DB_USERNAME}'] interval: 10s From c6ccf9f876121a7ff259d077a022f1915dd844a9 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Wed, 27 Nov 2024 12:47:09 +0700 Subject: [PATCH 127/155] feat: add VOLUMES_PATH to environment variables in GitHub Actions workflow --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b883793..3b820e7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,6 +38,7 @@ jobs: echo "ADMIN_EMAIL=${{ secrets.ADMIN_EMAIL }}" >> .env echo "ADMIN_PASSWORD=${{ secrets.ADMIN_PASSWORD }}" >> .env echo "AI_URL=${{ secrets.AI_URL }}" >> .env + echo "VOLUMES_PATH=${{ secrets.VOLUMES_PATH }}" >> .env cat .env - name: Running Docker Compose From 550dfd287311e4ac27a50661d217983e6ffc3dc7 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Wed, 27 Nov 2024 17:22:57 +0700 Subject: [PATCH 128/155] fix: update GitHub Actions workflow permissions and modify volume path in docker-compose.yml --- .github/workflows/main.yml | 5 ++--- docker-compose.yml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3b820e7..303f127 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,13 +1,13 @@ name: Build and deploy to an VM on: - push: + push: branches: - dev workflow_dispatch: permissions: - contents: read + contents: write jobs: build-and-deploy: @@ -38,7 +38,6 @@ jobs: echo "ADMIN_EMAIL=${{ secrets.ADMIN_EMAIL }}" >> .env echo "ADMIN_PASSWORD=${{ secrets.ADMIN_PASSWORD }}" >> .env echo "AI_URL=${{ secrets.AI_URL }}" >> .env - echo "VOLUMES_PATH=${{ secrets.VOLUMES_PATH }}" >> .env cat .env - name: Running Docker Compose diff --git a/docker-compose.yml b/docker-compose.yml index 03bbcbb..72ca334 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ services: POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: ${DB_DATABASE} volumes: - - ${VOLUMES_PATH}:/var/lib/postgresql/data + - ./postgres-data:/var/lib/postgresql/data healthcheck: test: ['CMD-SHELL', 'pg_isready -U ${DB_USERNAME}'] interval: 10s From 047779b2bbde6275ae2dc6cb70d19a6e2ba06d7b Mon Sep 17 00:00:00 2001 From: ganthepro Date: Wed, 27 Nov 2024 17:27:58 +0700 Subject: [PATCH 129/155] feat: add step to switch to root user in GitHub Actions workflow --- .github/workflows/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 303f127..ac48a07 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,6 +13,9 @@ jobs: build-and-deploy: runs-on: self-hosted steps: + - name: switch to root + run: sudo -s + - name: Copy Repository uses: actions/checkout@v4 From 21e5994a7b11d3012619939f675bd0f663d03146 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Wed, 27 Nov 2024 17:33:13 +0700 Subject: [PATCH 130/155] feat: add step to identify current user in GitHub Actions workflow --- .github/workflows/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ac48a07..13db5f8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,6 +13,9 @@ jobs: build-and-deploy: runs-on: self-hosted steps: + - name: 'who am i' + run: whoami + - name: switch to root run: sudo -s From 7db60d29df78fefb035a73c15283ca99135b63c9 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Wed, 27 Nov 2024 17:34:44 +0700 Subject: [PATCH 131/155] fix: remove unnecessary 'who am i' step from GitHub Actions workflow --- .github/workflows/main.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 13db5f8..ac48a07 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,9 +13,6 @@ jobs: build-and-deploy: runs-on: self-hosted steps: - - name: 'who am i' - run: whoami - - name: switch to root run: sudo -s From e85a2f5a27a6b9a348914c298668ea2f23b4c468 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Wed, 27 Nov 2024 18:17:50 +0700 Subject: [PATCH 132/155] feat: find exam-answer with pretest --- .../dtos/exam-answer-pretest-response.dto.ts | 131 ++++++++ .../dtos/exam-answer-response.dto.ts | 16 +- src/exam-answer/exam-answer.controller.ts | 155 ++++++++++ src/exam-answer/exam-answer.module.ts | 2 + src/exam-answer/exam-answer.service.ts | 291 +++++++++++++++++- src/exam-attempt/exam-attempt.controller.ts | 3 +- src/exam/exam.controller.ts | 3 +- src/pretest/pretest.controller.ts | 9 +- src/pretest/pretest.service.ts | 2 +- .../question-option.controller.ts | 9 +- src/question/question.controller.ts | 14 +- 11 files changed, 596 insertions(+), 39 deletions(-) create mode 100644 src/exam-answer/dtos/exam-answer-pretest-response.dto.ts diff --git a/src/exam-answer/dtos/exam-answer-pretest-response.dto.ts b/src/exam-answer/dtos/exam-answer-pretest-response.dto.ts new file mode 100644 index 0000000..7e621e1 --- /dev/null +++ b/src/exam-answer/dtos/exam-answer-pretest-response.dto.ts @@ -0,0 +1,131 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { ExamAnswer } from '../exam-answer.entity'; +import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response.dto'; +import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; +import { Question } from 'src/question/question.entity'; +import { QuestionOption } from 'src/question-option/question-option.entity'; +import { ExamAttemptResponseDto } from 'src/exam-attempt/dtos/exam-attempt-response.dto'; + +export class ExamAnswerPretestResponseDto { + @ApiProperty({ + description: 'Exam Answer ID', + type: String, + example: '123e4567-e89b-12d3-a456-426614174000', + }) + id: string; + + @ApiProperty({ + description: 'Exam Attempt Data', + type: ExamAttemptResponseDto, + example: { + score: '0', + id: '195abd69-0c8f-4fd3-88cb-e02e08917b1f', + status: 'in_progress', + startedAt: '2024-11-27T11:11:10.292Z', + submittedAt: null, + user: { + id: 'a12e1e37-3504-4711-a389-09a1734d7b1c', + }, + }, + }) + examAttempt: ExamAttemptResponseDto; + + @ApiProperty({ + description: 'Question Data', + type: Question, + example: { + points: 1, + orderIndex: 1, + id: 'b43d7571-f371-48a9-bee0-542f6fe154bb', + question: + 'What is the primary function of a microprocessor in a computer system?', + type: 'pretest', + pretest: { + timeLimit: 20, + id: 'b269a451-87a9-41f8-86be-d5c3ecdfa934', + title: 'Biology', + description: 'This course is an introduction to biology', + passingScore: 3, + maxAttempts: 1, + user: { + id: 'b269a451-87a9-41f8-86be-d5c3ecdfa934', + }, + }, + }, + }) + question: Question; + + @ApiProperty({ + description: 'Select Question Data', + type: QuestionOption, + example: { + id: '9d77c1d9-b0d1-4025-880c-48073e9dc7d5', + questionId: '1e251a62-6339-4a59-bb56-e338f1dae55b', + isCorrect: false, + explanation: 'Rock is not biology.', + }, + }) + selectedOption: QuestionOption; + + @ApiProperty({ + description: 'Answer text', + type: String, + example: 'biology', + }) + answerText: string; + + @ApiProperty({ + description: 'Is answer correct?', + type: Boolean, + example: false, + }) + isCorrect: boolean; + + @ApiProperty({ + description: 'Points in this answer', + type: Number, + example: 0, + }) + points: number = 0; + + @ApiProperty({ + description: 'Exam created date', + type: Date, + example: new Date(), + }) + createdAt: Date; + + @ApiProperty({ + description: 'Exam updated date', + type: Date, + example: new Date(), + }) + updatedAt: Date; + + constructor(examAnswer: ExamAnswer) { + this.id = examAnswer.id; + this.examAttempt = examAnswer.examAttempt; + this.question = examAnswer.question; + this.selectedOption = examAnswer.selectedOption; + this.isCorrect = examAnswer.isCorrect; + this.points = examAnswer.points; + this.createdAt = examAnswer.createdAt; + this.updatedAt = examAnswer.updatedAt; + } +} + +export class PaginatedExamAnswerPretestResponseDto extends PaginatedResponse( + ExamAnswerPretestResponseDto, +) { + constructor( + examAnswer: ExamAnswer[], + total: number, + pageSize: number, + currentPage: number, + ) { + const examAnswerDtos = examAnswer.map( + (examAnswer) => new ExamAnswerPretestResponseDto(examAnswer), + ); + super(examAnswerDtos, total, pageSize, currentPage); + } +} diff --git a/src/exam-answer/dtos/exam-answer-response.dto.ts b/src/exam-answer/dtos/exam-answer-response.dto.ts index 2d929e7..ecc296e 100644 --- a/src/exam-answer/dtos/exam-answer-response.dto.ts +++ b/src/exam-answer/dtos/exam-answer-response.dto.ts @@ -4,6 +4,7 @@ import { PaginatedResponse } from 'src/shared/pagination/dtos/paginate-response. import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; import { Question } from 'src/question/question.entity'; import { QuestionOption } from 'src/question-option/question-option.entity'; +import { ExamAttemptResponseDto } from 'src/exam-attempt/dtos/exam-attempt-response.dto'; export class ExamAnswerResponseDto { @ApiProperty({ @@ -15,10 +16,19 @@ export class ExamAnswerResponseDto { @ApiProperty({ description: 'Exam Attempt Data', - type: ExamAttempt, - example: { id: '123e4567-e89b-12d3-a456-426614174000' }, + type: ExamAttemptResponseDto, + example: { + score: '0', + id: '195abd69-0c8f-4fd3-88cb-e02e08917b1f', + status: 'in_progress', + startedAt: '2024-11-27T11:11:10.292Z', + submittedAt: null, + user: { + id: 'a12e1e37-3504-4711-a389-09a1734d7b1c', + }, + }, }) - examAttempt: ExamAttempt; + examAttempt: ExamAttemptResponseDto; @ApiProperty({ description: 'Question Data', diff --git a/src/exam-answer/exam-answer.controller.ts b/src/exam-answer/exam-answer.controller.ts index bd2afe6..60e3fc7 100644 --- a/src/exam-answer/exam-answer.controller.ts +++ b/src/exam-answer/exam-answer.controller.ts @@ -25,6 +25,10 @@ import { Roles } from 'src/shared/decorators/role.decorator'; import { Role } from 'src/shared/enums'; import { CreateExamAnswerDto } from './dtos/create-exam-answer.dto'; import { UpdateExamAnswerDto } from './dtos/update-exam-answer.dto'; +import { + ExamAnswerPretestResponseDto, + PaginatedExamAnswerPretestResponseDto, +} from './dtos/exam-answer-pretest-response.dto'; @Controller('exam-answer') @ApiTags('ExamAnswer') @@ -73,6 +77,88 @@ export class ExamAnswerController { ); } + @Get('/pretest') + @Roles(Role.STUDENT, Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all exam answers with pretest', + type: PaginatedExamAnswerPretestResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findAllExamAnswerPretest( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return await this.examAnswerService.findAllExamAnswerPretest( + request.user.id, + request.user.role, + { + page: query.page, + limit: query.limit, + search: query.search, + }, + ); + } + + @Get('/user') + @Roles(Role.STUDENT, Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all exam answers by user id', + type: PaginatedExamAnswerResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findExamAnswerByUserId( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return await this.examAnswerService.findExamAnswerByUserId( + request.user.id, + request.user.role, + { + page: query.page, + limit: query.limit, + search: query.search, + }, + ); + } + @Get(':id') @ApiResponse({ status: HttpStatus.OK, @@ -100,6 +186,75 @@ export class ExamAnswerController { return new ExamAnswerResponseDto(examAnswer); } + @Get('/pretest/user') + @Roles(Role.STUDENT, Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all exam answers with pretest by user id', + type: PaginatedExamAnswerPretestResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findExamAnswerPretestByUserId( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return await this.examAnswerService.findExamAnswerPretestByUserId( + request.user.id, + request.user.role, + { + page: query.page, + limit: query.limit, + search: query.search, + }, + ); + } + + @Get('/pretest/:pretestId') + @Roles(Role.STUDENT, Role.ADMIN) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns an exam answer with pretest', + type: ExamAnswerPretestResponseDto, + }) + async findOneExamAnswerPretest( + @Req() request: AuthenticatedRequest, + @Param( + 'pretestId', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + pretestId: string, + ): Promise { + const examAnswer = await this.examAnswerService.findOneExamAnswerPretest( + request.user.id, + request.user.role, + { + where: { id: pretestId }, + }, + ); + return new ExamAnswerPretestResponseDto(examAnswer); + } + @Get('question/:questionId') @ApiResponse({ status: HttpStatus.OK, diff --git a/src/exam-answer/exam-answer.module.ts b/src/exam-answer/exam-answer.module.ts index 6197e1c..8545e9a 100644 --- a/src/exam-answer/exam-answer.module.ts +++ b/src/exam-answer/exam-answer.module.ts @@ -9,6 +9,7 @@ import { ExamAnswerController } from './exam-answer.controller'; import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; import { Question } from 'src/question/question.entity'; import { QuestionOption } from 'src/question-option/question-option.entity'; +import { User } from 'src/user/user.entity'; @Module({ imports: [ @@ -18,6 +19,7 @@ import { QuestionOption } from 'src/question-option/question-option.entity'; ExamAttempt, Question, QuestionOption, + User, ]), ], controllers: [ExamAnswerController], diff --git a/src/exam-answer/exam-answer.service.ts b/src/exam-answer/exam-answer.service.ts index 566a854..2d05c95 100644 --- a/src/exam-answer/exam-answer.service.ts +++ b/src/exam-answer/exam-answer.service.ts @@ -4,26 +4,25 @@ import { NotFoundException, BadRequestException, ConflictException, - ForbiddenException, } from '@nestjs/common'; import { FindOneOptions, FindOptionsSelect, FindOptionsWhere, ILike, - Not, Repository, } from 'typeorm'; import { ExamAnswer } from './exam-answer.entity'; -import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { PaginatedExamAnswerResponseDto } from './dtos/exam-answer-response.dto'; import { createPagination } from 'src/shared/pagination'; -import { CourseStatus, ExamStatus, QuestionType, Role } from 'src/shared/enums'; +import { CourseStatus, ExamStatus, Role } from 'src/shared/enums'; import { CreateExamAnswerDto } from './dtos/create-exam-answer.dto'; import { Question } from 'src/question/question.entity'; import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; import { QuestionOption } from 'src/question-option/question-option.entity'; import { UpdateExamAnswerDto } from './dtos/update-exam-answer.dto'; +import { PaginatedExamAnswerPretestResponseDto } from './dtos/exam-answer-pretest-response.dto'; +import { User } from 'src/user/user.entity'; @Injectable() export class ExamAnswerService { @@ -36,6 +35,8 @@ export class ExamAnswerService { private readonly questionRepository: Repository, @Inject('QuestionOptionRepository') private readonly questionOptionRepository: Repository, + @Inject('UserRepository') + private readonly userRepository: Repository, ) {} async findAll( @@ -63,7 +64,12 @@ export class ExamAnswerService { ); const exam = await find({ where: whereCondition, - relations: ['examAttempt', 'question', 'selectedOption'], + relations: [ + 'examAttempt', + 'question', + 'selectedOption', + 'examAttempt.user', + ], select: { examAttempt: this.selectPopulateExamAttempt(), question: this.selectPopulateQuestion(), @@ -172,7 +178,12 @@ export class ExamAnswerService { const examAnswer = await this.examAnswerRepository.findOne({ ...options, where, - relations: ['examAttempt', 'question', 'selectedOption'], + relations: [ + 'examAttempt', + 'question', + 'selectedOption', + 'examAttempt.user', + ], select: { examAttempt: this.selectPopulateExamAttempt(), question: this.selectPopulateQuestion(), @@ -223,7 +234,12 @@ export class ExamAnswerService { return await find({ where: whereCondition, - relations: ['examAttempt', 'question', 'selectedOption'], + relations: [ + 'examAttempt', + 'question', + 'selectedOption', + 'examAttempt.user', + ], select: { examAttempt: this.selectPopulateExamAttempt(), question: this.selectPopulateQuestion(), @@ -268,7 +284,12 @@ export class ExamAnswerService { return await find({ where: whereCondition, - relations: ['examAttempt', 'question', 'selectedOption'], + relations: [ + 'examAttempt', + 'question', + 'selectedOption', + 'examAttempt.user', + ], select: { examAttempt: this.selectPopulateExamAttempt(), question: this.selectPopulateQuestion(), @@ -277,6 +298,55 @@ export class ExamAnswerService { }).run(); } + async findExamAnswerByUserId( + userId: string, + role: Role, + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.examAnswerRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateConditionForePretest( + userId, + role, + search, + ); + whereCondition['examAttempt.user'] = { id: userId }; + + const user = await this.userRepository.findOne({ + where: { id: userId }, + }); + + if (!user) { + throw new NotFoundException('User not found.'); + } + + return await find({ + where: whereCondition, + relations: [ + 'examAttempt', + 'question', + 'selectedOption', + 'examAttempt.user', + ], + select: { + examAttempt: this.selectPopulateExamAttempt(), + question: this.selectPopulateQuestionPretest(), + selectedOption: this.selectPopulateSelectedOption(), + }, + }).run(); + } + async createExamAnswer( createExamAnswerDto: CreateExamAnswerDto, ): Promise { @@ -357,7 +427,12 @@ export class ExamAnswerService { if (!examAnswer) throw new BadRequestException("Can't update exam answer"); return await this.examAnswerRepository.findOne({ where: { id }, - relations: ['examAttempt', 'question', 'selectedOption'], + relations: [ + 'examAttempt', + 'question', + 'selectedOption', + 'examAttempt.user', + ], select: { examAttempt: this.selectPopulateExamAttempt(), question: this.selectPopulateQuestion(), @@ -387,6 +462,9 @@ export class ExamAnswerService { status: true, startedAt: true, submittedAt: true, + user: { + id: true, + }, }; } @@ -408,4 +486,199 @@ export class ExamAnswerService { questionId: true, }; } + + async findAllExamAnswerPretest( + userId: string, + role: Role, + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.examAnswerRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateConditionForePretest( + userId, + role, + search, + ); + const exam = await find({ + where: whereCondition, + relations: [ + 'examAttempt', + 'question', + 'selectedOption', + 'question.pretest', + 'question.pretest.user', + ], + select: { + examAttempt: this.selectPopulateExamAttempt(), + question: this.selectPopulateQuestionPretest(), + selectedOption: this.selectPopulateSelectedOption(), + }, + }).run(); + + return exam; + } + + private validateAndCreateConditionForePretest( + userId: string, + role: Role, + search: string, + ): FindOptionsWhere | FindOptionsWhere[] { + const baseSearch = search ? { answerText: ILike(`%${search}%`) } : {}; + + if (role === Role.STUDENT) { + return [ + { + ...baseSearch, + question: { + pretest: { + user: { + id: userId, + }, + }, + }, + }, + ]; + } + + if (role === Role.ADMIN) { + return { ...baseSearch }; + } + + return [ + { + ...baseSearch, + question: { + pretest: { + user: { + id: userId, + }, + }, + }, + }, + ]; + } + + async findOneExamAnswerPretest( + userId: string, + role: Role, + options: FindOneOptions = {}, + ): Promise { + const whereCondition = this.validateAndCreateConditionForePretest( + userId, + role, + '', + ); + + const where = Array.isArray(whereCondition) + ? [ + { ...whereCondition[0], ...options.where }, + { ...whereCondition[1], ...options.where }, + ] + : { ...whereCondition, ...options.where }; + + const examAnswer = await this.examAnswerRepository.findOne({ + ...options, + where, + relations: [ + 'examAttempt', + 'question', + 'selectedOption', + 'question.pretest', + 'question.pretest.user', + ], + select: { + examAttempt: this.selectPopulateExamAttempt(), + question: this.selectPopulateQuestionPretest(), + selectedOption: this.selectPopulateSelectedOption(), + }, + }); + + if (!examAnswer) { + throw new NotFoundException('Exam answer not found'); + } + + return examAnswer; + } + + async findExamAnswerPretestByUserId( + userId: string, + role: Role, + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.examAnswerRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateConditionForePretest( + userId, + role, + search, + ); + whereCondition['question.pretest.user'] = { id: userId }; + + const user = await this.userRepository.findOne({ + where: { id: userId }, + }); + + if (!user) { + throw new NotFoundException('User not found.'); + } + + return await find({ + where: whereCondition, + relations: [ + 'examAttempt', + 'question', + 'selectedOption', + 'question.pretest', + 'question.pretest.user', + ], + select: { + examAttempt: this.selectPopulateExamAttempt(), + question: this.selectPopulateQuestionPretest(), + selectedOption: this.selectPopulateSelectedOption(), + }, + }).run(); + } + + private selectPopulateQuestionPretest(): FindOptionsSelect { + return { + id: true, + question: true, + type: true, + points: true, + orderIndex: true, + pretest: { + id: true, + title: true, + description: true, + timeLimit: true, + passingScore: true, + maxAttempts: true, + user: { + id: true, + }, + }, + }; + } } diff --git a/src/exam-attempt/exam-attempt.controller.ts b/src/exam-attempt/exam-attempt.controller.ts index 6dc1b74..ce9487d 100644 --- a/src/exam-attempt/exam-attempt.controller.ts +++ b/src/exam-attempt/exam-attempt.controller.ts @@ -325,8 +325,7 @@ export class ExamAttemptController { } @Delete(':id') - @Roles(Role.TEACHER) - @Roles(Role.ADMIN) + @Roles(Role.TEACHER, Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, description: 'Delete an exam', diff --git a/src/exam/exam.controller.ts b/src/exam/exam.controller.ts index effb96a..94ada0f 100644 --- a/src/exam/exam.controller.ts +++ b/src/exam/exam.controller.ts @@ -144,8 +144,7 @@ export class ExamController { } @Delete(':id') - @Roles(Role.TEACHER) - @Roles(Role.ADMIN) + @Roles(Role.TEACHER, Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, description: 'Delete an exam', diff --git a/src/pretest/pretest.controller.ts b/src/pretest/pretest.controller.ts index cf11247..b520da7 100644 --- a/src/pretest/pretest.controller.ts +++ b/src/pretest/pretest.controller.ts @@ -33,8 +33,7 @@ import { UpdatePretestDto } from './dtos/update-pretest.dto'; export class PretestController { constructor(private readonly pretestService: PretestService) {} @Get() - @Roles(Role.STUDENT) - @Roles(Role.ADMIN) + @Roles(Role.STUDENT, Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, description: 'Returns all pretests', @@ -75,8 +74,7 @@ export class PretestController { } @Get(':id') - @Roles(Role.STUDENT) - @Roles(Role.ADMIN) + @Roles(Role.STUDENT, Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, description: 'Returns a pretest', @@ -149,8 +147,7 @@ export class PretestController { } @Delete(':id') - @Roles(Role.STUDENT) - @Roles(Role.ADMIN) + @Roles(Role.STUDENT, Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, description: 'Delete a pretest', diff --git a/src/pretest/pretest.service.ts b/src/pretest/pretest.service.ts index cf2e449..d2218df 100644 --- a/src/pretest/pretest.service.ts +++ b/src/pretest/pretest.service.ts @@ -274,7 +274,7 @@ export class PretestService { Object.entries(data.choices).map(([key, value]) => { const createQuestionOptionDto = { questionId: question.id, - optionText: `${key}. ${value}`, + optionText: `${value}`, isCorrect: key === data.answer, explanation: '', }; diff --git a/src/question-option/question-option.controller.ts b/src/question-option/question-option.controller.ts index be398bb..d04e3fe 100644 --- a/src/question-option/question-option.controller.ts +++ b/src/question-option/question-option.controller.ts @@ -75,8 +75,7 @@ export class QuestionOptionController { } @Get('/pretest') - @Roles(Role.STUDENT) - @Roles(Role.ADMIN) + @Roles(Role.STUDENT, Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, description: 'Returns all question option pretest', @@ -144,8 +143,7 @@ export class QuestionOptionController { } @Get('pretest/:questionId') - @Roles(Role.STUDENT) - @Roles(Role.ADMIN) + @Roles(Role.STUDENT, Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, description: 'Returns all question options in pretest id', @@ -291,8 +289,7 @@ export class QuestionOptionController { } @Delete(':id') - @Roles(Role.TEACHER) - @Roles(Role.ADMIN) + @Roles(Role.TEACHER, Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, description: 'Delete a question option', diff --git a/src/question/question.controller.ts b/src/question/question.controller.ts index fdc93a3..1da9c85 100644 --- a/src/question/question.controller.ts +++ b/src/question/question.controller.ts @@ -25,10 +25,7 @@ import { Roles } from 'src/shared/decorators/role.decorator'; import { Role } from 'src/shared/enums'; import { CreateQuestionDto } from './dtos/create-question.dto'; import { UpdateQuestionDto } from './dtos/update-question.dto'; -import { - PaginatedQuestionPretestResponseDto, - QuestionPretestResponseDto, -} from './dtos/question-pretest-response.dto'; +import { PaginatedQuestionPretestResponseDto } from './dtos/question-pretest-response.dto'; @Controller('question') @Injectable() @@ -78,8 +75,7 @@ export class QuestionController { } @Get('/pretest') - @Roles(Role.STUDENT) - @Roles(Role.TEACHER) + @Roles(Role.STUDENT, Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, description: 'Returns all questions pretest', @@ -147,8 +143,7 @@ export class QuestionController { } @Get('pretest/:pretestId') - @Roles(Role.STUDENT) - @Roles(Role.TEACHER) + @Roles(Role.STUDENT, Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, description: 'Returns all questions pretest with pretest id', @@ -293,8 +288,7 @@ export class QuestionController { } @Delete(':id') - @Roles(Role.TEACHER) - @Roles(Role.ADMIN) + @Roles(Role.TEACHER, Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, description: 'Delete an question', From c3531e513ee3e84958e1d49e6c7f2ddedc1f2a03 Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Wed, 27 Nov 2024 19:28:01 +0700 Subject: [PATCH 133/155] feat: add API_URL to dotenv and update related configurations in chapter service and dtos --- .env.example | 3 ++- src/chapter/chapter.entity.ts | 2 +- src/chapter/chapter.service.ts | 5 ++--- src/chapter/dtos/chapter-response.dto.ts | 7 ------- src/chapter/dtos/create-chapter.dto.ts | 9 --------- src/shared/configs/dotenv.config.ts | 1 + src/shared/constants/global-config.constant.ts | 1 + 7 files changed, 7 insertions(+), 21 deletions(-) diff --git a/.env.example b/.env.example index 59b9a08..7ca62c7 100644 --- a/.env.example +++ b/.env.example @@ -16,4 +16,5 @@ AWS_REGION=yourregion AWS_BUCKET_NAME=yourbucketname ADMIN_EMAIL=admin@gmail.com ADMIN_PASSWORD=P@ssword! -AI_URL=https://localhost:5000 \ No newline at end of file +AI_URL=https://localhost:5000 +API_URL=https://api.edusaig.com \ No newline at end of file diff --git a/src/chapter/chapter.entity.ts b/src/chapter/chapter.entity.ts index 4ab7942..1633153 100644 --- a/src/chapter/chapter.entity.ts +++ b/src/chapter/chapter.entity.ts @@ -58,7 +58,7 @@ export class Chapter { @Column({ type: String, - nullable: false, + nullable: true, }) summary: string; diff --git a/src/chapter/chapter.service.ts b/src/chapter/chapter.service.ts index 3d87115..3a27113 100644 --- a/src/chapter/chapter.service.ts +++ b/src/chapter/chapter.service.ts @@ -252,10 +252,9 @@ export class ChapterService { try { const transcriptionResponse = await firstValueFrom( this.httpService.post( - `${this.configService.get(GLOBAL_CONFIG.AI_URL)}/asr-public`, + `${this.configService.get(GLOBAL_CONFIG.AI_URL)}/asr`, { - url: `http://localhost:${this.configService.get(GLOBAL_CONFIG.PORT)}/chapter/${id}/video`, - language: 'en' + url: `${this.configService.getOrThrow(GLOBAL_CONFIG.API_URL)}/chapter/${id}/video`, }, { headers: { diff --git a/src/chapter/dtos/chapter-response.dto.ts b/src/chapter/dtos/chapter-response.dto.ts index ef1132e..6dcd57c 100644 --- a/src/chapter/dtos/chapter-response.dto.ts +++ b/src/chapter/dtos/chapter-response.dto.ts @@ -32,12 +32,6 @@ export class ChapterResponseDto { }) content: string; - @ApiProperty({ - description: 'Chapter summary', - type: String, - example: 'This chapter is an introduction to programming', - }) - summary: string; @ApiProperty({ description: 'Chapter duration', @@ -78,7 +72,6 @@ export class ChapterResponseDto { this.title = chapter.title; this.description = chapter.description; this.content = chapter.content; - this.summary = chapter.summary; this.duration = chapter.duration; this.createdAt = chapter.createdAt; this.updatedAt = chapter.updatedAt; diff --git a/src/chapter/dtos/create-chapter.dto.ts b/src/chapter/dtos/create-chapter.dto.ts index b516661..3a50dfb 100644 --- a/src/chapter/dtos/create-chapter.dto.ts +++ b/src/chapter/dtos/create-chapter.dto.ts @@ -36,15 +36,6 @@ export class CreateChapterDto { }) content: string; - @IsNotEmpty() - @IsString() - @ApiProperty({ - description: 'Chapter summary', - type: String, - example: 'This chapter is an introduction to programming', - }) - summary: string; - @IsNotEmpty() @IsNumber() @ApiProperty({ diff --git a/src/shared/configs/dotenv.config.ts b/src/shared/configs/dotenv.config.ts index 878a256..f49d547 100644 --- a/src/shared/configs/dotenv.config.ts +++ b/src/shared/configs/dotenv.config.ts @@ -28,4 +28,5 @@ export const dotenvConfig = Joi.object({ ADMIN_EMAIL: Joi.string().required(), ADMIN_PASSWORD: Joi.string().required(), AI_URL: Joi.string().required(), + API_URL: Joi.string().required(), }); diff --git a/src/shared/constants/global-config.constant.ts b/src/shared/constants/global-config.constant.ts index f9d9202..33b9435 100644 --- a/src/shared/constants/global-config.constant.ts +++ b/src/shared/constants/global-config.constant.ts @@ -19,4 +19,5 @@ export const GLOBAL_CONFIG = { ADMIN_EMAIL: 'ADMIN_EMAIL', ADMIN_PASSWORD: 'ADMIN_PASSWORD', AI_URL: 'AI_URL', + API_URL: 'API_URL', }; From 086ef773e3b6d2a3e2d8ec72501eda738b5906be Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Wed, 27 Nov 2024 19:38:05 +0700 Subject: [PATCH 134/155] feat: find exam-answer by pretest id --- src/exam-answer/exam-answer.controller.ts | 36 +++++++++++++--- src/exam-answer/exam-answer.module.ts | 2 + src/exam-answer/exam-answer.service.ts | 52 ++++++++++++++--------- 3 files changed, 63 insertions(+), 27 deletions(-) diff --git a/src/exam-answer/exam-answer.controller.ts b/src/exam-answer/exam-answer.controller.ts index 60e3fc7..b7e6e76 100644 --- a/src/exam-answer/exam-answer.controller.ts +++ b/src/exam-answer/exam-answer.controller.ts @@ -231,11 +231,31 @@ export class ExamAnswerController { @Roles(Role.STUDENT, Role.ADMIN) @ApiResponse({ status: HttpStatus.OK, - description: 'Returns an exam answer with pretest', - type: ExamAnswerPretestResponseDto, + description: 'Returns all exam answers with pretest by pretest id', + type: PaginatedExamAnswerPretestResponseDto, + isArray: true, }) - async findOneExamAnswerPretest( + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findExamAnswerPretest( @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, @Param( 'pretestId', new ParseUUIDPipe({ @@ -244,15 +264,17 @@ export class ExamAnswerController { }), ) pretestId: string, - ): Promise { - const examAnswer = await this.examAnswerService.findOneExamAnswerPretest( + ): Promise { + return await this.examAnswerService.findExamAnswerPretestByPretestId( request.user.id, request.user.role, + pretestId, { - where: { id: pretestId }, + page: query.page, + limit: query.limit, + search: query.search, }, ); - return new ExamAnswerPretestResponseDto(examAnswer); } @Get('question/:questionId') diff --git a/src/exam-answer/exam-answer.module.ts b/src/exam-answer/exam-answer.module.ts index 8545e9a..d38a90d 100644 --- a/src/exam-answer/exam-answer.module.ts +++ b/src/exam-answer/exam-answer.module.ts @@ -10,6 +10,7 @@ import { ExamAttempt } from 'src/exam-attempt/exam-attempt.entity'; import { Question } from 'src/question/question.entity'; import { QuestionOption } from 'src/question-option/question-option.entity'; import { User } from 'src/user/user.entity'; +import { Pretest } from 'src/pretest/pretest.entity'; @Module({ imports: [ @@ -20,6 +21,7 @@ import { User } from 'src/user/user.entity'; Question, QuestionOption, User, + Pretest, ]), ], controllers: [ExamAnswerController], diff --git a/src/exam-answer/exam-answer.service.ts b/src/exam-answer/exam-answer.service.ts index 2d05c95..9d72138 100644 --- a/src/exam-answer/exam-answer.service.ts +++ b/src/exam-answer/exam-answer.service.ts @@ -23,6 +23,7 @@ import { QuestionOption } from 'src/question-option/question-option.entity'; import { UpdateExamAnswerDto } from './dtos/update-exam-answer.dto'; import { PaginatedExamAnswerPretestResponseDto } from './dtos/exam-answer-pretest-response.dto'; import { User } from 'src/user/user.entity'; +import { Pretest } from 'src/pretest/pretest.entity'; @Injectable() export class ExamAnswerService { @@ -37,6 +38,8 @@ export class ExamAnswerService { private readonly questionOptionRepository: Repository, @Inject('UserRepository') private readonly userRepository: Repository, + @Inject('PretestRepository') + private readonly pretestRepository: Repository, ) {} async findAll( @@ -569,27 +572,42 @@ export class ExamAnswerService { ]; } - async findOneExamAnswerPretest( + async findExamAnswerPretestByPretestId( userId: string, role: Role, - options: FindOneOptions = {}, - ): Promise { + pretestId: string, + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.examAnswerRepository, { + page, + limit, + }); + const whereCondition = this.validateAndCreateConditionForePretest( userId, role, - '', + search, ); + whereCondition['question.pretest'] = { id: pretestId }; - const where = Array.isArray(whereCondition) - ? [ - { ...whereCondition[0], ...options.where }, - { ...whereCondition[1], ...options.where }, - ] - : { ...whereCondition, ...options.where }; + const pretest = await this.pretestRepository.findOne({ + where: { id: pretestId }, + }); - const examAnswer = await this.examAnswerRepository.findOne({ - ...options, - where, + if (!pretest) { + throw new NotFoundException('Pretest not found.'); + } + + return await find({ + where: whereCondition, relations: [ 'examAttempt', 'question', @@ -602,13 +620,7 @@ export class ExamAnswerService { question: this.selectPopulateQuestionPretest(), selectedOption: this.selectPopulateSelectedOption(), }, - }); - - if (!examAnswer) { - throw new NotFoundException('Exam answer not found'); - } - - return examAnswer; + }).run(); } async findExamAnswerPretestByUserId( From bb2d0a141255c1307b2ed3c1cf48a3c35a449f77 Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Wed, 27 Nov 2024 20:14:00 +0700 Subject: [PATCH 135/155] feat: rename transcribe endpoint to summarize; implement summarize logic in chapter service with error handling --- src/chapter/chapter.controller.ts | 12 ++++---- src/chapter/chapter.service.ts | 50 +++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/chapter/chapter.controller.ts b/src/chapter/chapter.controller.ts index 7fbc606..8ec9b44 100644 --- a/src/chapter/chapter.controller.ts +++ b/src/chapter/chapter.controller.ts @@ -183,6 +183,7 @@ export class ChapterController { search: query.search }); } + @Get('module/:moduleId') @ApiParam({ name: 'moduleId', @@ -254,20 +255,21 @@ export class ChapterController { where: { id }, }); } - @Get('transcribe/:id') + @Get('summarize/:id') @ApiResponse({ status: HttpStatus.OK, type: ChapterResponseDto, - description: 'Transcribe a chapter', + description: 'Summarize a chapter', }) @ApiParam({ name: 'id', type: String, description: 'Chapter ID', }) - @ApiBearerAuth() - async transcribe(@Param('id', ParseUUIDPipe) id: string) { - return await this.chapterService.transcribeAudio(id); + @Public() + async summarize(@Param('id', ParseUUIDPipe) id: string) { + const chapter = await this.chapterService.summarize(id); + return new ChapterResponseDto(chapter); } @Get(':id') diff --git a/src/chapter/chapter.service.ts b/src/chapter/chapter.service.ts index 3a27113..798b2e8 100644 --- a/src/chapter/chapter.service.ts +++ b/src/chapter/chapter.service.ts @@ -247,6 +247,28 @@ export class ChapterService { return result; } + async summarize(id: string): Promise { + try { + const transcribeResult = await this.transcribeAudio(id); + + if (!transcribeResult?.transcription) { + throw new BadRequestException('Invalid transcription response'); + } + + const summarize = await this.summarizeChapter(transcribeResult.transcription); + + this.chapterRepository.update(id, { summary: summarize }); + + return await this.findOne({ where: { id } }); + } catch (error) { + console.error('Full error details:', error); + throw new InternalServerErrorException( + 'Failed to process audio summarization', + { cause: error } + ); + } + } + async transcribeAudio(id: string): Promise { try { @@ -280,7 +302,35 @@ export class ChapterService { throw new InternalServerErrorException('Error processing transcription request'); } } + private async summarizeChapter(content: string): Promise { + try { + const summarizeResponse = await firstValueFrom( + this.httpService.post( + `${this.configService.get(GLOBAL_CONFIG.AI_URL)}/summarize`, + { + content: `${content}`, + }, + { + headers: { + 'Content-Type': 'application/json' + } + } + ) + ); + if (!summarizeResponse.data) { + throw new BadRequestException('Failed to get summarization'); + } + return summarizeResponse.data; + } catch (error) { + if (error.response) { + throw new InternalServerErrorException( + error.response.data || 'Summarization service error' + ); + } + throw new InternalServerErrorException('Error processing summarization request'); + } + } private async validateOrderIndex( moduleId: string, orderIndex: number, From c57b316bd284e0253bcbc819260c6cf8bfc717d0 Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Wed, 27 Nov 2024 20:58:25 +0700 Subject: [PATCH 136/155] feat: add summary field to ChapterResponseDto and implement SummarizeResponseDto for chapter summarization logic --- src/chapter/chapter.service.ts | 29 ++++++++++++++-------- src/chapter/dtos/chapter-response.dto.ts | 7 ++++++ src/chapter/dtos/summarize-response.dto.ts | 14 +++++++++++ src/user-reward/user-reward.entity.ts | 2 +- src/user-reward/user-reward.service.ts | 2 -- 5 files changed, 41 insertions(+), 13 deletions(-) create mode 100644 src/chapter/dtos/summarize-response.dto.ts diff --git a/src/chapter/chapter.service.ts b/src/chapter/chapter.service.ts index 798b2e8..8fe9c4a 100644 --- a/src/chapter/chapter.service.ts +++ b/src/chapter/chapter.service.ts @@ -23,6 +23,7 @@ import { HttpService } from '@nestjs/axios'; import { TranscribeResponseDto } from './dtos/transcribe-response.dto'; import { firstValueFrom } from 'rxjs'; import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; +import { SummarizeResponseDto } from './dtos/summarize-response.dto'; @Injectable() export class ChapterService { @@ -250,14 +251,14 @@ export class ChapterService { async summarize(id: string): Promise { try { const transcribeResult = await this.transcribeAudio(id); - + if (!transcribeResult?.transcription) { throw new BadRequestException('Invalid transcription response'); } - + const summarize = await this.summarizeChapter(transcribeResult.transcription); - - this.chapterRepository.update(id, { summary: summarize }); + + this.chapterRepository.update(id, { summary: summarize.summary }); return await this.findOne({ where: { id } }); } catch (error) { @@ -302,14 +303,12 @@ export class ChapterService { throw new InternalServerErrorException('Error processing transcription request'); } } - private async summarizeChapter(content: string): Promise { + private async summarizeChapter(content: string): Promise { try { const summarizeResponse = await firstValueFrom( this.httpService.post( `${this.configService.get(GLOBAL_CONFIG.AI_URL)}/summarize`, - { - content: `${content}`, - }, + { content }, { headers: { 'Content-Type': 'application/json' @@ -317,10 +316,21 @@ export class ChapterService { } ) ); + if (!summarizeResponse.data) { throw new BadRequestException('Failed to get summarization'); } - return summarizeResponse.data; + + let summaryText = summarizeResponse.data.summary; + try { + const parsedSummary = JSON.parse(summaryText); + summaryText = parsedSummary.summary || summaryText; + } catch (e) { + } + + return { + summary: summaryText + }; } catch (error) { if (error.response) { throw new InternalServerErrorException( @@ -329,7 +339,6 @@ export class ChapterService { } throw new InternalServerErrorException('Error processing summarization request'); } - } private async validateOrderIndex( moduleId: string, diff --git a/src/chapter/dtos/chapter-response.dto.ts b/src/chapter/dtos/chapter-response.dto.ts index 6dcd57c..ef1132e 100644 --- a/src/chapter/dtos/chapter-response.dto.ts +++ b/src/chapter/dtos/chapter-response.dto.ts @@ -32,6 +32,12 @@ export class ChapterResponseDto { }) content: string; + @ApiProperty({ + description: 'Chapter summary', + type: String, + example: 'This chapter is an introduction to programming', + }) + summary: string; @ApiProperty({ description: 'Chapter duration', @@ -72,6 +78,7 @@ export class ChapterResponseDto { this.title = chapter.title; this.description = chapter.description; this.content = chapter.content; + this.summary = chapter.summary; this.duration = chapter.duration; this.createdAt = chapter.createdAt; this.updatedAt = chapter.updatedAt; diff --git a/src/chapter/dtos/summarize-response.dto.ts b/src/chapter/dtos/summarize-response.dto.ts new file mode 100644 index 0000000..4f6bc6f --- /dev/null +++ b/src/chapter/dtos/summarize-response.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsNotEmpty, IsString } from "class-validator"; + +export class SummarizeResponseDto { + + @IsNotEmpty() + @IsString() + @ApiProperty({ + description: 'summary', + type: String, + example: 'this is a summary of the chapter', + }) + summary: string; +} \ No newline at end of file diff --git a/src/user-reward/user-reward.entity.ts b/src/user-reward/user-reward.entity.ts index afaddc0..a0fc63e 100644 --- a/src/user-reward/user-reward.entity.ts +++ b/src/user-reward/user-reward.entity.ts @@ -37,7 +37,7 @@ export class UserReward { }) status: UserRewardStatus; - //redeemed at + @CreateDateColumn({ type: 'timestamp with time zone', nullable: false, diff --git a/src/user-reward/user-reward.service.ts b/src/user-reward/user-reward.service.ts index bc9f7e6..cf31862 100644 --- a/src/user-reward/user-reward.service.ts +++ b/src/user-reward/user-reward.service.ts @@ -39,9 +39,7 @@ export class UserRewardService { if (reward.stock <= 0) throw new BadRequestException('reward not enough'); if (reward.stock == 1) this.rewardRepository.update(rewardId, { status: Status.INACTIVE }); - //update reward stock this.rewardRepository.update(rewardId, { stock: reward.stock - 1 }); - //decrease user points this.userRepository.update(userId, { points: user.points - reward.points }); const newUserReward = new UserReward(); newUserReward.user = user; From 91f7b363a3484d78bd14faa941c62c4bc9c371c1 Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Wed, 27 Nov 2024 21:00:39 +0700 Subject: [PATCH 137/155] refactor: remove unnecessary @ApiBearerAuth() decorators from course controller methods --- src/course/course.controller.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/course/course.controller.ts b/src/course/course.controller.ts index a8efbaf..125cc02 100644 --- a/src/course/course.controller.ts +++ b/src/course/course.controller.ts @@ -418,7 +418,6 @@ export class CourseController { type: CourseResponseDto, description: 'Create course', }) - @ApiBearerAuth() async create( @Req() request: AuthenticatedRequest, @Body() createCourseDto: CreateCourseDto, @@ -451,7 +450,6 @@ export class CourseController { type: CourseResponseDto, description: 'Update course by id', }) - @ApiBearerAuth() async update( @Req() request: AuthenticatedRequest, @Body() updateCourseDto: UpdateCourseDto, @@ -488,7 +486,6 @@ export class CourseController { status: HttpStatus.OK, description: 'Delete course by id', }) - @ApiBearerAuth() async delete( @Param( 'id', From 176c0f2f26a7c684084b44a7ac455624e129cd5c Mon Sep 17 00:00:00 2001 From: ganthepro Date: Wed, 27 Nov 2024 21:27:28 +0700 Subject: [PATCH 138/155] feat: add VideoGuard for chapter video access control --- src/chapter/chapter.controller.ts | 11 ++++- src/chapter/guards/video.guard.ts | 76 +++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 src/chapter/guards/video.guard.ts diff --git a/src/chapter/chapter.controller.ts b/src/chapter/chapter.controller.ts index ba94715..6d8d674 100644 --- a/src/chapter/chapter.controller.ts +++ b/src/chapter/chapter.controller.ts @@ -48,6 +48,7 @@ import { Folder } from 'src/file/enums/folder.enum'; import { FileInterceptor } from '@nestjs/platform-express'; import { EnrollmentService } from 'src/enrollment/enrollment.service'; import { Public } from 'src/shared/decorators/public.decorator'; +import { VideoGuard } from './guards/video.guard'; @Controller('chapter') @ApiTags('Chapters') @@ -57,14 +58,13 @@ export class ChapterController { private readonly chapterService: ChapterService, private readonly courseModuleService: CourseModuleService, private readonly fileService: FileService, - private readonly enrollmentService: EnrollmentService, ) { } @Get(':id/video') @ApiParam({ name: 'id', type: String, - description: 'Course id', + description: 'chapter id', }) @Public() @ApiResponse({ @@ -72,6 +72,13 @@ export class ChapterController { description: 'Get chapter video', type: StreamableFile, }) + @ApiQuery({ + name: 'token', + type: String, + required: false, + description: 'Access token', + }) + @UseGuards(VideoGuard) async getVideo( @Param( 'id', diff --git a/src/chapter/guards/video.guard.ts b/src/chapter/guards/video.guard.ts new file mode 100644 index 0000000..1f46bfd --- /dev/null +++ b/src/chapter/guards/video.guard.ts @@ -0,0 +1,76 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + UnauthorizedException, + NotFoundException, + ForbiddenException, +} from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; +import { JwtPayloadDto } from 'src/auth/dtos/jwt-payload.dto'; +import { ConfigService } from '@nestjs/config'; +import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; +import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; +import { EnrollmentService } from 'src/enrollment/enrollment.service'; +import { Role } from 'src/shared/enums'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Chapter } from '../chapter.entity'; +import { Repository } from 'typeorm'; + +@Injectable() +export class VideoGuard implements CanActivate { + constructor( + @InjectRepository(Chapter) + private readonly chapterRepository: Repository, + private readonly jwtService: JwtService, + private readonly configService: ConfigService, + private readonly enrollmentService: EnrollmentService, + ) {} + + async canActivate(context: ExecutionContext): Promise { + const request: AuthenticatedRequest = context.switchToHttp().getRequest(); + const chapterId = request.params.id; + const chapter = await this.chapterRepository.findOne({ + where: { id: chapterId }, + relations: { + module: { + course: { + teacher: true, + }, + }, + } + }); + if (!chapter) throw new NotFoundException('Chapter not found'); + if (chapter.isPreview) return true; + const token = request.query.token as string; + if (!token) throw new UnauthorizedException('Unauthorized access'); + try { + request.user = await this.jwtService.verifyAsync(token, { + secret: this.configService.getOrThrow( + GLOBAL_CONFIG.JWT_ACCESS_SECRET, + ), + }); + } catch (error) { + throw new UnauthorizedException('Unauthorized access'); + } + const userId = request.user.id; + const courseId = chapter.module.course.id; + switch (request.user.role) { + case Role.TEACHER: + if (chapter.module.course.teacher.id !== userId) + throw new ForbiddenException('Insufficient permissions'); + break; + case Role.STUDENT: + const enrollment = await this.enrollmentService.findOne({ + course: { id: courseId }, + user: { id: userId }, + }); + if (!enrollment) + throw new ForbiddenException('Insufficient permissions'); + break; + default: + throw new ForbiddenException('Insufficient permissions'); + } + return true; + } +} From 7ddc460cfde15aef38be796c224383803309bafd Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Wed, 27 Nov 2024 22:22:01 +0700 Subject: [PATCH 139/155] feat: add correctAnswer in exan-answer --- .../dtos/exam-answer-pretest-response.dto.ts | 13 +++++++++ .../dtos/exam-answer-response.dto.ts | 13 +++++++++ src/exam-answer/exam-answer.entity.ts | 14 ++++++++++ src/exam-answer/exam-answer.service.ts | 28 +++++++++++++++++++ src/question-option/question-option.entity.ts | 5 ++++ 5 files changed, 73 insertions(+) diff --git a/src/exam-answer/dtos/exam-answer-pretest-response.dto.ts b/src/exam-answer/dtos/exam-answer-pretest-response.dto.ts index 7e621e1..95ae943 100644 --- a/src/exam-answer/dtos/exam-answer-pretest-response.dto.ts +++ b/src/exam-answer/dtos/exam-answer-pretest-response.dto.ts @@ -67,6 +67,18 @@ export class ExamAnswerPretestResponseDto { }) selectedOption: QuestionOption; + @ApiProperty({ + description: 'Select Question Data', + type: QuestionOption, + example: { + id: '9d77c1d9-b0d1-4025-880c-48073e9dc7d5', + questionId: '1e251a62-6339-4a59-bb56-e338f1dae55b', + isCorrect: true, + explanation: 'Rock is not biology.', + }, + }) + correctAnswer: QuestionOption; + @ApiProperty({ description: 'Answer text', type: String, @@ -107,6 +119,7 @@ export class ExamAnswerPretestResponseDto { this.examAttempt = examAnswer.examAttempt; this.question = examAnswer.question; this.selectedOption = examAnswer.selectedOption; + this.correctAnswer = examAnswer.correctAnswer; this.isCorrect = examAnswer.isCorrect; this.points = examAnswer.points; this.createdAt = examAnswer.createdAt; diff --git a/src/exam-answer/dtos/exam-answer-response.dto.ts b/src/exam-answer/dtos/exam-answer-response.dto.ts index ecc296e..a0463b6 100644 --- a/src/exam-answer/dtos/exam-answer-response.dto.ts +++ b/src/exam-answer/dtos/exam-answer-response.dto.ts @@ -55,6 +55,18 @@ export class ExamAnswerResponseDto { }) selectedOption: QuestionOption; + @ApiProperty({ + description: 'Select Question Data', + type: QuestionOption, + example: { + id: '9d77c1d9-b0d1-4025-880c-48073e9dc7d5', + questionId: '1e251a62-6339-4a59-bb56-e338f1dae55b', + isCorrect: true, + explanation: 'Rock is not biology.', + }, + }) + correctAnswer: QuestionOption; + @ApiProperty({ description: 'Answer text', type: String, @@ -95,6 +107,7 @@ export class ExamAnswerResponseDto { this.examAttempt = examAnswer.examAttempt; this.question = examAnswer.question; this.selectedOption = examAnswer.selectedOption; + this.correctAnswer = examAnswer.correctAnswer; this.isCorrect = examAnswer.isCorrect; this.points = examAnswer.points; this.createdAt = examAnswer.createdAt; diff --git a/src/exam-answer/exam-answer.entity.ts b/src/exam-answer/exam-answer.entity.ts index 75366c3..30b5dc6 100644 --- a/src/exam-answer/exam-answer.entity.ts +++ b/src/exam-answer/exam-answer.entity.ts @@ -52,6 +52,20 @@ export class ExamAnswer { @Column({ name: 'question_option_id' }) selectedOptionId: string; + @ManyToOne( + () => QuestionOption, + (questionOption) => questionOption.examAnswerCorrect, + { + onDelete: 'CASCADE', + nullable: false, + }, + ) + @JoinColumn({ name: 'correct_answer_id' }) + correctAnswer: QuestionOption; + + @Column({ name: 'correct_answer_id' }) + correctAnswerId: string; + @Column({ nullable: false, }) diff --git a/src/exam-answer/exam-answer.service.ts b/src/exam-answer/exam-answer.service.ts index 9d72138..c3fd87b 100644 --- a/src/exam-answer/exam-answer.service.ts +++ b/src/exam-answer/exam-answer.service.ts @@ -72,11 +72,13 @@ export class ExamAnswerService { 'question', 'selectedOption', 'examAttempt.user', + 'correctAnswer', ], select: { examAttempt: this.selectPopulateExamAttempt(), question: this.selectPopulateQuestion(), selectedOption: this.selectPopulateSelectedOption(), + correctAnswer: this.selectPopulateSelectedOption(), }, }).run(); @@ -186,11 +188,13 @@ export class ExamAnswerService { 'question', 'selectedOption', 'examAttempt.user', + 'correctAnswer', ], select: { examAttempt: this.selectPopulateExamAttempt(), question: this.selectPopulateQuestion(), selectedOption: this.selectPopulateSelectedOption(), + correctAnswer: this.selectPopulateSelectedOption(), }, }); @@ -242,11 +246,13 @@ export class ExamAnswerService { 'question', 'selectedOption', 'examAttempt.user', + 'correctAnswer', ], select: { examAttempt: this.selectPopulateExamAttempt(), question: this.selectPopulateQuestion(), selectedOption: this.selectPopulateSelectedOption(), + correctAnswer: this.selectPopulateSelectedOption(), }, }).run(); } @@ -292,11 +298,13 @@ export class ExamAnswerService { 'question', 'selectedOption', 'examAttempt.user', + 'correctAnswer', ], select: { examAttempt: this.selectPopulateExamAttempt(), question: this.selectPopulateQuestion(), selectedOption: this.selectPopulateSelectedOption(), + correctAnswer: this.selectPopulateSelectedOption(), }, }).run(); } @@ -341,11 +349,13 @@ export class ExamAnswerService { 'question', 'selectedOption', 'examAttempt.user', + 'correctAnswer', ], select: { examAttempt: this.selectPopulateExamAttempt(), question: this.selectPopulateQuestionPretest(), selectedOption: this.selectPopulateSelectedOption(), + correctAnswer: this.selectPopulateSelectedOption(), }, }).run(); } @@ -368,6 +378,14 @@ export class ExamAnswerService { if (!question) throw new NotFoundException('Not Found Question'); + const correctAnswer = await this.questionOptionRepository.findOne({ + where: { questionId: selectedOption.questionId, isCorrect: true }, + select: this.selectPopulateSelectedOption(), + }); + + if (!correctAnswer) + throw new NotFoundException('Not found correct answer in this question'); + const examAttempt = createExamAnswerDto.examAttemptId ? await this.examAttemptRepository.findOne({ where: { id: createExamAnswerDto.examAttemptId }, @@ -380,6 +398,8 @@ export class ExamAnswerService { selectedOption, question, examAttempt, + correctAnswer, + correctAnswerId: correctAnswer.id, }); if (!examAnswer) throw new NotFoundException("Can't create exam"); @@ -435,11 +455,13 @@ export class ExamAnswerService { 'question', 'selectedOption', 'examAttempt.user', + 'correctAnswer', ], select: { examAttempt: this.selectPopulateExamAttempt(), question: this.selectPopulateQuestion(), selectedOption: this.selectPopulateSelectedOption(), + correctAnswer: this.selectPopulateSelectedOption(), }, }); } @@ -521,11 +543,13 @@ export class ExamAnswerService { 'selectedOption', 'question.pretest', 'question.pretest.user', + 'correctAnswer', ], select: { examAttempt: this.selectPopulateExamAttempt(), question: this.selectPopulateQuestionPretest(), selectedOption: this.selectPopulateSelectedOption(), + correctAnswer: this.selectPopulateSelectedOption(), }, }).run(); @@ -614,11 +638,13 @@ export class ExamAnswerService { 'selectedOption', 'question.pretest', 'question.pretest.user', + 'correctAnswer', ], select: { examAttempt: this.selectPopulateExamAttempt(), question: this.selectPopulateQuestionPretest(), selectedOption: this.selectPopulateSelectedOption(), + correctAnswer: this.selectPopulateSelectedOption(), }, }).run(); } @@ -664,11 +690,13 @@ export class ExamAnswerService { 'selectedOption', 'question.pretest', 'question.pretest.user', + 'correctAnswer', ], select: { examAttempt: this.selectPopulateExamAttempt(), question: this.selectPopulateQuestionPretest(), selectedOption: this.selectPopulateSelectedOption(), + correctAnswer: this.selectPopulateSelectedOption(), }, }).run(); } diff --git a/src/question-option/question-option.entity.ts b/src/question-option/question-option.entity.ts index 3bd9f54..971c13d 100644 --- a/src/question-option/question-option.entity.ts +++ b/src/question-option/question-option.entity.ts @@ -31,6 +31,11 @@ export class QuestionOption { }) examAnswer: ExamAnswer[]; + @OneToMany(() => ExamAnswer, (examAnswer) => examAnswer.correctAnswer, { + cascade: true, + }) + examAnswerCorrect: ExamAnswer[]; + @Column({ nullable: false, }) From 959234ba42cc643fdad6c443f102f422b9e0fd53 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Wed, 27 Nov 2024 22:27:09 +0700 Subject: [PATCH 140/155] feat: populate optiontext in questionOption --- src/exam-answer/dtos/exam-answer-pretest-response.dto.ts | 2 ++ src/exam-answer/dtos/exam-answer-response.dto.ts | 1 + src/exam-answer/exam-answer.service.ts | 1 + 3 files changed, 4 insertions(+) diff --git a/src/exam-answer/dtos/exam-answer-pretest-response.dto.ts b/src/exam-answer/dtos/exam-answer-pretest-response.dto.ts index 95ae943..f72fc85 100644 --- a/src/exam-answer/dtos/exam-answer-pretest-response.dto.ts +++ b/src/exam-answer/dtos/exam-answer-pretest-response.dto.ts @@ -63,6 +63,7 @@ export class ExamAnswerPretestResponseDto { questionId: '1e251a62-6339-4a59-bb56-e338f1dae55b', isCorrect: false, explanation: 'Rock is not biology.', + optionText: 'ROM', }, }) selectedOption: QuestionOption; @@ -75,6 +76,7 @@ export class ExamAnswerPretestResponseDto { questionId: '1e251a62-6339-4a59-bb56-e338f1dae55b', isCorrect: true, explanation: 'Rock is not biology.', + optionText: 'ROM', }, }) correctAnswer: QuestionOption; diff --git a/src/exam-answer/dtos/exam-answer-response.dto.ts b/src/exam-answer/dtos/exam-answer-response.dto.ts index a0463b6..53e0a6d 100644 --- a/src/exam-answer/dtos/exam-answer-response.dto.ts +++ b/src/exam-answer/dtos/exam-answer-response.dto.ts @@ -63,6 +63,7 @@ export class ExamAnswerResponseDto { questionId: '1e251a62-6339-4a59-bb56-e338f1dae55b', isCorrect: true, explanation: 'Rock is not biology.', + optionText: 'ROM', }, }) correctAnswer: QuestionOption; diff --git a/src/exam-answer/exam-answer.service.ts b/src/exam-answer/exam-answer.service.ts index c3fd87b..e8087d3 100644 --- a/src/exam-answer/exam-answer.service.ts +++ b/src/exam-answer/exam-answer.service.ts @@ -509,6 +509,7 @@ export class ExamAnswerService { isCorrect: true, explanation: true, questionId: true, + optionText: true, }; } From be244eac77ef27142e30859642d6432a6182cbbb Mon Sep 17 00:00:00 2001 From: khris-xp Date: Thu, 28 Nov 2024 15:09:01 +0700 Subject: [PATCH 141/155] feat: connect ai with roadmap service --- pnpm-lock.yaml | 8130 ++++++++--------- src/course/course.entity.ts | 2 - src/pretest/pretest.service.ts | 26 +- src/roadmap/dtos/create-roadmp-ai.dto.ts | 13 + src/roadmap/roadmap.controller.ts | 3 +- src/roadmap/roadmap.module.ts | 13 +- src/roadmap/roadmap.service.ts | 102 +- .../user-background.controller.ts | 2 +- 8 files changed, 3699 insertions(+), 4592 deletions(-) create mode 100644 src/roadmap/dtos/create-roadmp-ai.dto.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 560d1e4..ea79ffb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,3675 +1,182 @@ -lockfileVersion: '9.0' +lockfileVersion: '6.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false -importers: - - .: - dependencies: - '@aws-sdk/client-s3': - specifier: ^3.693.0 - version: 3.693.0 - '@nestjs/axios': - specifier: ^3.1.2 - version: 3.1.2(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(axios@1.7.7)(rxjs@7.8.1) - '@nestjs/class-validator': - specifier: 0.13.1 - version: 0.13.1 - '@nestjs/common': - specifier: ^10.0.0 - version: 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/config': - specifier: ^3.3.0 - version: 3.3.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(rxjs@7.8.1) - '@nestjs/core': - specifier: ^10.0.0 - version: 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/jwt': - specifier: ^10.2.0 - version: 10.2.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1)) - '@nestjs/mapped-types': - specifier: ^2.0.6 - version: 2.0.6(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) - '@nestjs/platform-express': - specifier: ^10.0.0 - version: 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) - '@nestjs/swagger': - specifier: ^8.0.5 - version: 8.0.5(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) - '@nestjs/typeorm': - specifier: ^10.0.2 - version: 10.0.2(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3))) - '@smithy/types': - specifier: ^3.7.1 - version: 3.7.1 - argon2: - specifier: ^0.41.1 - version: 0.41.1 - axios: - specifier: ^1.7.7 - version: 1.7.7 - class-transformer: - specifier: ^0.5.1 - version: 0.5.1 - class-validator: - specifier: ^0.14.1 - version: 0.14.1 - dotenv: - specifier: ^16.4.5 - version: 16.4.5 - joi: - specifier: ^17.13.3 - version: 17.13.3 - mysql2: - specifier: ^3.11.4 - version: 3.11.4 - pg: - specifier: ^8.13.1 - version: 8.13.1 - reflect-metadata: - specifier: ^0.2.0 - version: 0.2.0 - rxjs: - specifier: ^7.8.1 - version: 7.8.1 - typeorm: - specifier: ^0.3.20 - version: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) - typeorm-extension: - specifier: ^3.6.3 - version: 3.6.3(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3))) - devDependencies: - '@nestjs/cli': - specifier: ^10.0.0 - version: 10.0.0 - '@nestjs/schematics': - specifier: ^10.0.0 - version: 10.0.0(chokidar@3.5.3)(typescript@5.1.3) - '@nestjs/testing': - specifier: ^10.0.0 - version: 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0) - '@types/express': - specifier: ^5.0.0 - version: 5.0.0 - '@types/jest': - specifier: ^29.5.2 - version: 29.5.2 - '@types/multer': - specifier: ^1.4.12 - version: 1.4.12 - '@types/node': - specifier: ^20.3.1 - version: 20.3.1 - '@types/supertest': - specifier: ^6.0.0 - version: 6.0.0 - '@typescript-eslint/eslint-plugin': - specifier: ^8.0.0 - version: 8.0.0(@typescript-eslint/parser@8.0.0(eslint@8.0.0)(typescript@5.1.3))(eslint@8.0.0)(typescript@5.1.3) - '@typescript-eslint/parser': - specifier: ^8.0.0 - version: 8.0.0(eslint@8.0.0)(typescript@5.1.3) - eslint: - specifier: ^8.0.0 - version: 8.0.0 - eslint-config-prettier: - specifier: ^9.0.0 - version: 9.0.0(eslint@8.0.0) - eslint-plugin-prettier: - specifier: ^5.0.0 - version: 5.0.0(@types/eslint@9.6.1)(eslint-config-prettier@9.0.0(eslint@8.0.0))(eslint@8.0.0)(prettier@3.0.0) - jest: - specifier: ^29.5.0 - version: 29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) - prettier: - specifier: ^3.0.0 - version: 3.0.0 - source-map-support: - specifier: ^0.5.21 - version: 0.5.21 - supertest: - specifier: ^7.0.0 - version: 7.0.0 - ts-jest: - specifier: ^29.1.0 - version: 29.1.0(@babel/core@7.26.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)))(typescript@5.1.3) - ts-loader: - specifier: ^9.4.3 - version: 9.4.3(typescript@5.1.3)(webpack@5.87.0) - ts-node: - specifier: ^10.9.1 - version: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) - tsconfig-paths: - specifier: ^4.2.0 - version: 4.2.0 - typescript: - specifier: ^5.1.3 - version: 5.1.3 - -packages: - - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - - '@angular-devkit/core@16.1.0': - resolution: {integrity: sha512-mrWpuDvttmhrCGcLc68RIXKtTzUhkBTsE5ZZFZNO1+FSC+vO/ZpyCpPd6C+6coM68NfXYjHlms5XF6KbxeGn/Q==} - engines: {node: ^16.14.0 || >=18.10.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - peerDependencies: - chokidar: ^3.5.2 - peerDependenciesMeta: - chokidar: - optional: true - - '@angular-devkit/schematics-cli@16.1.0': - resolution: {integrity: sha512-siBpRDmMMV7NB+NvaDHeJ4doHoSkFwIywwFj8GXnBCtobyxrBl1EyG1cKK+FHRydYtyYIk8FEoOpJA9oE9S2hg==} - engines: {node: ^16.14.0 || >=18.10.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - hasBin: true - - '@angular-devkit/schematics@16.1.0': - resolution: {integrity: sha512-LM35PH9DT3eQRSZgrkk2bx1ZQjjVh8BCByTlr37/c+FnF9mNbeBsa1YkxrlsN/CwO+045OwEwRHnkM9Zcx0U/A==} - engines: {node: ^16.14.0 || >=18.10.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} - - '@aws-crypto/crc32@5.2.0': - resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} - engines: {node: '>=16.0.0'} - - '@aws-crypto/crc32c@5.2.0': - resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} - - '@aws-crypto/sha1-browser@5.2.0': - resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} - - '@aws-crypto/sha256-browser@5.2.0': - resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} - - '@aws-crypto/sha256-js@5.2.0': - resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} - engines: {node: '>=16.0.0'} - - '@aws-crypto/supports-web-crypto@5.2.0': - resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} - - '@aws-crypto/util@5.2.0': - resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - - '@aws-sdk/client-s3@3.693.0': - resolution: {integrity: sha512-vgGI2e0Q6pzyhqfrSysi+sk/i+Nl+lMon67oqj/57RcCw9daL1/inpS+ADuwHpiPWkrg+U0bOXnmHjkLeTslJg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/client-sso-oidc@3.693.0': - resolution: {integrity: sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sts': ^3.693.0 - - '@aws-sdk/client-sso@3.693.0': - resolution: {integrity: sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/client-sts@3.693.0': - resolution: {integrity: sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/core@3.693.0': - resolution: {integrity: sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-env@3.693.0': - resolution: {integrity: sha512-hMUZaRSF7+iBKZfBHNLihFs9zvpM1CB8MBOTnTp5NGCVkRYF3SB2LH+Kcippe0ats4qCyB1eEoyQX99rERp2iQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-http@3.693.0': - resolution: {integrity: sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-ini@3.693.0': - resolution: {integrity: sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sts': ^3.693.0 - - '@aws-sdk/credential-provider-node@3.693.0': - resolution: {integrity: sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-process@3.693.0': - resolution: {integrity: sha512-cvxQkrTWHHjeHrPlj7EWXPnFSq8x7vMx+Zn1oTsMpCY445N9KuzjfJTkmNGwU2GT6rSZI9/0MM02aQvl5bBBTQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-sso@3.693.0': - resolution: {integrity: sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/credential-provider-web-identity@3.693.0': - resolution: {integrity: sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sts': ^3.693.0 - - '@aws-sdk/middleware-bucket-endpoint@3.693.0': - resolution: {integrity: sha512-cPIa+lxMYiFRHtxKfNIVSFGO6LSgZCk42pu3d7KGwD6hu6vXRD5B2/DD3rPcEH1zgl2j0Kx1oGAV7SRXKHSFag==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-expect-continue@3.693.0': - resolution: {integrity: sha512-MuK/gsJWpHz6Tv0CqTCS+QNOxLa2RfPh1biVCu/uO3l7kA0TjQ/C+tfgKvLXeH103tuDrOVINK+bt2ENmI3SWg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-flexible-checksums@3.693.0': - resolution: {integrity: sha512-xkS6zjuE11ob93H9t65kHzphXcUMnN2SmIm2wycUPg+hi8Q6DJA6U2p//6oXkrr9oHy1QvwtllRd7SAd63sFKQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-host-header@3.693.0': - resolution: {integrity: sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-location-constraint@3.693.0': - resolution: {integrity: sha512-eDAExTZ9uNIP7vs2JCVCOuWJauGueisBSn+Ovt7UvvuEUp6KOIJqn8oFxWmyUQu2GvbG4OcaTLgbqD95YHTB0Q==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-logger@3.693.0': - resolution: {integrity: sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-recursion-detection@3.693.0': - resolution: {integrity: sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-sdk-s3@3.693.0': - resolution: {integrity: sha512-5A++RBjJ3guyq5pbYs+Oq5hMlA8CK2OWaHx09cxVfhHWl/RoaY8DXrft4gnhoUEBrrubyMw7r9j7RIMLvS58kg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-ssec@3.693.0': - resolution: {integrity: sha512-Ro5vzI7SRgEeuoMk3fKqFjGv6mG4c7VsSCDwnkiasmafQFBTPvUIpgmu2FXMHqW/OthvoiOzpSrlJ9Bwlx2f8A==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/middleware-user-agent@3.693.0': - resolution: {integrity: sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/region-config-resolver@3.693.0': - resolution: {integrity: sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/signature-v4-multi-region@3.693.0': - resolution: {integrity: sha512-s7zbbsoVIriTR4ZGaateKuTqz6ddpazAyHvjk7I9kd+NvGNPiuAI18UdbuiiRI6K5HuYKf1ah6mKWFGPG15/kQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/token-providers@3.693.0': - resolution: {integrity: sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==} - engines: {node: '>=16.0.0'} - peerDependencies: - '@aws-sdk/client-sso-oidc': ^3.693.0 - - '@aws-sdk/types@3.692.0': - resolution: {integrity: sha512-RpNvzD7zMEhiKgmlxGzyXaEcg2khvM7wd5sSHVapOcrde1awQSOMGI4zKBQ+wy5TnDfrm170ROz/ERLYtrjPZA==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/util-arn-parser@3.693.0': - resolution: {integrity: sha512-WC8x6ca+NRrtpAH64rWu+ryDZI3HuLwlEr8EU6/dbC/pt+r/zC0PBoC15VEygUaBA+isppCikQpGyEDu0Yj7gQ==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/util-endpoints@3.693.0': - resolution: {integrity: sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/util-locate-window@3.693.0': - resolution: {integrity: sha512-ttrag6haJLWABhLqtg1Uf+4LgHWIMOVSYL+VYZmAp2v4PUGOwWmWQH0Zk8RM7YuQcLfH/EoR72/Yxz6A4FKcuw==} - engines: {node: '>=16.0.0'} - - '@aws-sdk/util-user-agent-browser@3.693.0': - resolution: {integrity: sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==} - - '@aws-sdk/util-user-agent-node@3.693.0': - resolution: {integrity: sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==} - engines: {node: '>=16.0.0'} - peerDependencies: - aws-crt: '>=1.0.0' - peerDependenciesMeta: - aws-crt: - optional: true - - '@aws-sdk/xml-builder@3.693.0': - resolution: {integrity: sha512-C/rPwJcqnV8VDr2/VtcQnymSpcfEEgH1Jm6V0VmfXNZFv4Qzf1eCS8nsec0gipYgZB+cBBjfXw5dAk6pJ8ubpw==} - engines: {node: '>=16.0.0'} - - '@babel/code-frame@7.26.2': - resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.26.2': - resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.26.0': - resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.26.2': - resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.25.9': - resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.25.9': - resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.26.0': - resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-plugin-utils@7.25.9': - resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.25.9': - resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.25.9': - resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.25.9': - resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.26.0': - resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.26.2': - resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-syntax-async-generators@7.8.4': - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-bigint@7.8.3': - resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-class-properties@7.12.13': - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-class-static-block@7.14.5': - resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-attributes@7.26.0': - resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-meta@7.10.4': - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-json-strings@7.8.3': - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-jsx@7.25.9': - resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-logical-assignment-operators@7.10.4': - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-numeric-separator@7.10.4': - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-object-rest-spread@7.8.3': - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-optional-catch-binding@7.8.3': - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-optional-chaining@7.8.3': - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-private-property-in-object@7.14.5': - resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-top-level-await@7.14.5': - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-typescript@7.25.9': - resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/template@7.25.9': - resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.25.9': - resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.26.0': - resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} - engines: {node: '>=6.9.0'} - - '@bcoe/v8-coverage@0.2.3': - resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - - '@colors/colors@1.5.0': - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} - engines: {node: '>=0.1.90'} - - '@cspotcode/source-map-support@0.8.1': - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - - '@eslint-community/eslint-utils@4.4.1': - resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - - '@eslint-community/regexpp@4.12.1': - resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/eslintrc@1.4.1': - resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@faker-js/faker@8.4.1': - resolution: {integrity: sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'} - - '@hapi/hoek@9.3.0': - resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} - - '@hapi/topo@5.1.0': - resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} - - '@humanwhocodes/config-array@0.6.0': - resolution: {integrity: sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead - - '@humanwhocodes/object-schema@1.2.1': - resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} - deprecated: Use @eslint/object-schema instead - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@istanbuljs/load-nyc-config@1.1.0': - resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} - engines: {node: '>=8'} - - '@istanbuljs/schema@0.1.3': - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - - '@jest/console@29.7.0': - resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/core@29.7.0': - resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - '@jest/environment@29.7.0': - resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/expect-utils@29.7.0': - resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/expect@29.7.0': - resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/fake-timers@29.7.0': - resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/globals@29.7.0': - resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/reporters@29.7.0': - resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/source-map@29.6.3': - resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/test-result@29.7.0': - resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/test-sequencer@29.7.0': - resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/transform@29.7.0': - resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jest/types@29.6.3': - resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jridgewell/gen-mapping@0.3.5': - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} - engines: {node: '>=6.0.0'} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - - '@jridgewell/source-map@0.3.6': - resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} - - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - - '@jridgewell/trace-mapping@0.3.9': - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - - '@lukeed/csprng@1.1.0': - resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} - engines: {node: '>=8'} - - '@microsoft/tsdoc@0.15.0': - resolution: {integrity: sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==} - - '@nestjs/axios@3.1.2': - resolution: {integrity: sha512-pFlfi4ZQsZtTNNhvgssbxjCHUd1nMpV3sXy/xOOB2uEJhw3M8j8SFR08gjFNil2we2Har7VCsXLfCkwbMHECFQ==} - peerDependencies: - '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 - axios: ^1.3.1 - rxjs: ^6.0.0 || ^7.0.0 - - '@nestjs/class-validator@0.13.1': - resolution: {integrity: sha512-Wwpg9KuGnkWOFleBPEFdHQKwqZsF/PFWQaeSaXwatkofgqD7N6AV5J30xIVgScDP+IcE+MdPGZJ38vHQ24KxPQ==} - - '@nestjs/cli@10.0.0': - resolution: {integrity: sha512-14pju3ejAAUpFe1iK99v/b7Bw96phBMV58GXTSm3TcdgaI4O7UTLXTbMiUNyU+LGr/1CPIfThcWqFyKhDIC9VQ==} - engines: {node: '>= 16'} - hasBin: true - peerDependencies: - '@swc/cli': ^0.1.62 - '@swc/core': ^1.3.62 - peerDependenciesMeta: - '@swc/cli': - optional: true - '@swc/core': - optional: true - - '@nestjs/common@10.0.0': - resolution: {integrity: sha512-Fa2GDQJrO5TTTcpISWfm0pdPS62V+8YbxeG5CA01zMUI+dCO3v3oFf+BSjqCGUUo7GDNzDsjAejwGXuqA54RPw==} - peerDependencies: - class-transformer: '*' - class-validator: '*' - reflect-metadata: ^0.1.12 - rxjs: ^7.1.0 - peerDependenciesMeta: - class-transformer: - optional: true - class-validator: - optional: true - - '@nestjs/config@3.3.0': - resolution: {integrity: sha512-pdGTp8m9d0ZCrjTpjkUbZx6gyf2IKf+7zlkrPNMsJzYZ4bFRRTpXrnj+556/5uiI6AfL5mMrJc2u7dB6bvM+VA==} - peerDependencies: - '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 - rxjs: ^7.1.0 - - '@nestjs/core@10.0.0': - resolution: {integrity: sha512-HFTdj4vsF+2qOaq97ZPRDle6Q/KyL5lmMah0/ZR0ie+e1/tnlvmlqw589xFACTemLJFFOjZMy763v+icO9u72w==} - peerDependencies: - '@nestjs/common': ^10.0.0 - '@nestjs/microservices': ^10.0.0 - '@nestjs/platform-express': ^10.0.0 - '@nestjs/websockets': ^10.0.0 - reflect-metadata: ^0.1.12 - rxjs: ^7.1.0 - peerDependenciesMeta: - '@nestjs/microservices': - optional: true - '@nestjs/platform-express': - optional: true - '@nestjs/websockets': - optional: true - - '@nestjs/jwt@10.2.0': - resolution: {integrity: sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==} - peerDependencies: - '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 - - '@nestjs/mapped-types@2.0.6': - resolution: {integrity: sha512-84ze+CPfp1OWdpRi1/lOu59hOhTz38eVzJvRKrg9ykRFwDz+XleKfMsG0gUqNZYFa6v53XYzeD+xItt8uDW7NQ==} - peerDependencies: - '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 - class-transformer: ^0.4.0 || ^0.5.0 - class-validator: ^0.13.0 || ^0.14.0 - reflect-metadata: ^0.1.12 || ^0.2.0 - peerDependenciesMeta: - class-transformer: - optional: true - class-validator: - optional: true - - '@nestjs/platform-express@10.0.0': - resolution: {integrity: sha512-jOQBPVpk7B4JFXZZwxHSsY6odIqZlea9CbqKzu/hfDyqRv+AwuJk5gprvvL6RpWAHNyRMH1r5/14bqcXD3+WGw==} - peerDependencies: - '@nestjs/common': ^10.0.0 - '@nestjs/core': ^10.0.0 - - '@nestjs/schematics@10.0.0': - resolution: {integrity: sha512-gfUy/N1m1paN33BXq4d7HoCM+zM4rFxYjqAb8jkrBfBHiwyEhHHozfX/aRy/kOnAcy/VP8v4Zs4HKKrbRRlHnw==} - peerDependencies: - typescript: '>=4.8.2' - - '@nestjs/swagger@8.0.5': - resolution: {integrity: sha512-ZmBdsbQNs3wIN5kCuvAVbz3/ULh3gi814oHTP49uTqAGi1aT0YSatUyncwQOHBOlRT+rwF+TNjoAsZ+twIk/Jw==} - peerDependencies: - '@fastify/static': ^6.0.0 || ^7.0.0 - '@nestjs/common': ^9.0.0 || ^10.0.0 - '@nestjs/core': ^9.0.0 || ^10.0.0 - class-transformer: '*' - class-validator: '*' - reflect-metadata: ^0.1.12 || ^0.2.0 - peerDependenciesMeta: - '@fastify/static': - optional: true - class-transformer: - optional: true - class-validator: - optional: true - - '@nestjs/testing@10.0.0': - resolution: {integrity: sha512-U5q3+svkddpdSk51ZFCEnFpQuWxAwE4ahsX77FrqqCAYidr7HUtL/BHYOVzI5H9vUH6BvJxMbfo3tiUXQl/2aA==} - peerDependencies: - '@nestjs/common': ^10.0.0 - '@nestjs/core': ^10.0.0 - '@nestjs/microservices': ^10.0.0 - '@nestjs/platform-express': ^10.0.0 - peerDependenciesMeta: - '@nestjs/microservices': - optional: true - '@nestjs/platform-express': - optional: true - - '@nestjs/typeorm@10.0.2': - resolution: {integrity: sha512-H738bJyydK4SQkRCTeh1aFBxoO1E9xdL/HaLGThwrqN95os5mEyAtK7BLADOS+vldP4jDZ2VQPLj4epWwRqCeQ==} - peerDependencies: - '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 - '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 - reflect-metadata: ^0.1.13 || ^0.2.0 - rxjs: ^7.2.0 - typeorm: ^0.3.0 - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@nuxtjs/opencollective@0.3.2': - resolution: {integrity: sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==} - engines: {node: '>=8.0.0', npm: '>=5.0.0'} - hasBin: true - - '@phc/format@1.0.0': - resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==} - engines: {node: '>=10'} - - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - '@pkgr/core@0.1.1': - resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - - '@scarf/scarf@1.4.0': - resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} - - '@sideway/address@4.1.5': - resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} - - '@sideway/formula@3.0.1': - resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} - - '@sideway/pinpoint@2.0.0': - resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} - - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - - '@sinonjs/commons@3.0.1': - resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} - - '@sinonjs/fake-timers@10.3.0': - resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} - - '@smithy/abort-controller@3.1.8': - resolution: {integrity: sha512-+3DOBcUn5/rVjlxGvUPKc416SExarAQ+Qe0bqk30YSUjbepwpS7QN0cyKUSifvLJhdMZ0WPzPP5ymut0oonrpQ==} - engines: {node: '>=16.0.0'} - - '@smithy/chunked-blob-reader-native@3.0.1': - resolution: {integrity: sha512-VEYtPvh5rs/xlyqpm5NRnfYLZn+q0SRPELbvBV+C/G7IQ+ouTuo+NKKa3ShG5OaFR8NYVMXls9hPYLTvIKKDrQ==} - - '@smithy/chunked-blob-reader@4.0.0': - resolution: {integrity: sha512-jSqRnZvkT4egkq/7b6/QRCNXmmYVcHwnJldqJ3IhVpQE2atObVJ137xmGeuGFhjFUr8gCEVAOKwSY79OvpbDaQ==} - - '@smithy/config-resolver@3.0.12': - resolution: {integrity: sha512-YAJP9UJFZRZ8N+UruTeq78zkdjUHmzsY62J4qKWZ4SXB4QXJ/+680EfXXgkYA2xj77ooMqtUY9m406zGNqwivQ==} - engines: {node: '>=16.0.0'} - - '@smithy/core@2.5.3': - resolution: {integrity: sha512-96uW8maifUSmehaeW7uydWn7wBc98NEeNI3zN8vqakGpyCQgzyJaA64Z4FCOUmAdCJkhppd/7SZ798Fo4Xx37g==} - engines: {node: '>=16.0.0'} - - '@smithy/credential-provider-imds@3.2.7': - resolution: {integrity: sha512-cEfbau+rrWF8ylkmmVAObOmjbTIzKyUC5TkBL58SbLywD0RCBC4JAUKbmtSm2w5KUJNRPGgpGFMvE2FKnuNlWQ==} - engines: {node: '>=16.0.0'} - - '@smithy/eventstream-codec@3.1.9': - resolution: {integrity: sha512-F574nX0hhlNOjBnP+noLtsPFqXnWh2L0+nZKCwcu7P7J8k+k+rdIDs+RMnrMwrzhUE4mwMgyN0cYnEn0G8yrnQ==} - - '@smithy/eventstream-serde-browser@3.0.13': - resolution: {integrity: sha512-Nee9m+97o9Qj6/XeLz2g2vANS2SZgAxV4rDBMKGHvFJHU/xz88x2RwCkwsvEwYjSX4BV1NG1JXmxEaDUzZTAtw==} - engines: {node: '>=16.0.0'} - - '@smithy/eventstream-serde-config-resolver@3.0.10': - resolution: {integrity: sha512-K1M0x7P7qbBUKB0UWIL5KOcyi6zqV5mPJoL0/o01HPJr0CSq3A9FYuJC6e11EX6hR8QTIR++DBiGrYveOu6trw==} - engines: {node: '>=16.0.0'} - - '@smithy/eventstream-serde-node@3.0.12': - resolution: {integrity: sha512-kiZymxXvZ4tnuYsPSMUHe+MMfc4FTeFWJIc0Q5wygJoUQM4rVHNghvd48y7ppuulNMbuYt95ah71pYc2+o4JOA==} - engines: {node: '>=16.0.0'} - - '@smithy/eventstream-serde-universal@3.0.12': - resolution: {integrity: sha512-1i8ifhLJrOZ+pEifTlF0EfZzMLUGQggYQ6WmZ4d5g77zEKf7oZ0kvh1yKWHPjofvOwqrkwRDVuxuYC8wVd662A==} - engines: {node: '>=16.0.0'} - - '@smithy/fetch-http-handler@4.1.1': - resolution: {integrity: sha512-bH7QW0+JdX0bPBadXt8GwMof/jz0H28I84hU1Uet9ISpzUqXqRQ3fEZJ+ANPOhzSEczYvANNl3uDQDYArSFDtA==} - - '@smithy/hash-blob-browser@3.1.9': - resolution: {integrity: sha512-wOu78omaUuW5DE+PVWXiRKWRZLecARyP3xcq5SmkXUw9+utgN8HnSnBfrjL2B/4ZxgqPjaAJQkC/+JHf1ITVaQ==} - - '@smithy/hash-node@3.0.10': - resolution: {integrity: sha512-3zWGWCHI+FlJ5WJwx73Mw2llYR8aflVyZN5JhoqLxbdPZi6UyKSdCeXAWJw9ja22m6S6Tzz1KZ+kAaSwvydi0g==} - engines: {node: '>=16.0.0'} - - '@smithy/hash-stream-node@3.1.9': - resolution: {integrity: sha512-3XfHBjSP3oDWxLmlxnt+F+FqXpL3WlXs+XXaB6bV9Wo8BBu87fK1dSEsyH7Z4ZHRmwZ4g9lFMdf08m9hoX1iRA==} - engines: {node: '>=16.0.0'} - - '@smithy/invalid-dependency@3.0.10': - resolution: {integrity: sha512-Lp2L65vFi+cj0vFMu2obpPW69DU+6O5g3086lmI4XcnRCG8PxvpWC7XyaVwJCxsZFzueHjXnrOH/E0pl0zikfA==} - - '@smithy/is-array-buffer@2.2.0': - resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} - engines: {node: '>=14.0.0'} - - '@smithy/is-array-buffer@3.0.0': - resolution: {integrity: sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==} - engines: {node: '>=16.0.0'} - - '@smithy/md5-js@3.0.10': - resolution: {integrity: sha512-m3bv6dApflt3fS2Y1PyWPUtRP7iuBlvikEOGwu0HsCZ0vE7zcIX+dBoh3e+31/rddagw8nj92j0kJg2TfV+SJA==} - - '@smithy/middleware-content-length@3.0.12': - resolution: {integrity: sha512-1mDEXqzM20yywaMDuf5o9ue8OkJ373lSPbaSjyEvkWdqELhFMyNNgKGWL/rCSf4KME8B+HlHKuR8u9kRj8HzEQ==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-endpoint@3.2.3': - resolution: {integrity: sha512-Hdl9296i/EMptaX7agrSzJZDiz5Y8XPUeBbctTmMtnCguGpqfU3jVsTUan0VLaOhsnquqWLL8Bl5HrlbVGT1og==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-retry@3.0.27': - resolution: {integrity: sha512-H3J/PjJpLL7Tt+fxDKiOD25sMc94YetlQhCnYeNmina2LZscAdu0ZEZPas/kwePHABaEtqp7hqa5S4UJgMs1Tg==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-serde@3.0.10': - resolution: {integrity: sha512-MnAuhh+dD14F428ubSJuRnmRsfOpxSzvRhaGVTvd/lrUDE3kxzCCmH8lnVTvoNQnV2BbJ4c15QwZ3UdQBtFNZA==} - engines: {node: '>=16.0.0'} - - '@smithy/middleware-stack@3.0.10': - resolution: {integrity: sha512-grCHyoiARDBBGPyw2BeicpjgpsDFWZZxptbVKb3CRd/ZA15F/T6rZjCCuBUjJwdck1nwUuIxYtsS4H9DDpbP5w==} - engines: {node: '>=16.0.0'} - - '@smithy/node-config-provider@3.1.11': - resolution: {integrity: sha512-URq3gT3RpDikh/8MBJUB+QGZzfS7Bm6TQTqoh4CqE8NBuyPkWa5eUXj0XFcFfeZVgg3WMh1u19iaXn8FvvXxZw==} - engines: {node: '>=16.0.0'} - - '@smithy/node-http-handler@3.3.1': - resolution: {integrity: sha512-fr+UAOMGWh6bn4YSEezBCpJn9Ukp9oR4D32sCjCo7U81evE11YePOQ58ogzyfgmjIO79YeOdfXXqr0jyhPQeMg==} - engines: {node: '>=16.0.0'} - - '@smithy/property-provider@3.1.10': - resolution: {integrity: sha512-n1MJZGTorTH2DvyTVj+3wXnd4CzjJxyXeOgnTlgNVFxaaMeT4OteEp4QrzF8p9ee2yg42nvyVK6R/awLCakjeQ==} - engines: {node: '>=16.0.0'} - - '@smithy/protocol-http@4.1.7': - resolution: {integrity: sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==} - engines: {node: '>=16.0.0'} - - '@smithy/querystring-builder@3.0.10': - resolution: {integrity: sha512-nT9CQF3EIJtIUepXQuBFb8dxJi3WVZS3XfuDksxSCSn+/CzZowRLdhDn+2acbBv8R6eaJqPupoI/aRFIImNVPQ==} - engines: {node: '>=16.0.0'} - - '@smithy/querystring-parser@3.0.10': - resolution: {integrity: sha512-Oa0XDcpo9SmjhiDD9ua2UyM3uU01ZTuIrNdZvzwUTykW1PM8o2yJvMh1Do1rY5sUQg4NDV70dMi0JhDx4GyxuQ==} - engines: {node: '>=16.0.0'} - - '@smithy/service-error-classification@3.0.10': - resolution: {integrity: sha512-zHe642KCqDxXLuhs6xmHVgRwy078RfqxP2wRDpIyiF8EmsWXptMwnMwbVa50lw+WOGNrYm9zbaEg0oDe3PTtvQ==} - engines: {node: '>=16.0.0'} - - '@smithy/shared-ini-file-loader@3.1.11': - resolution: {integrity: sha512-AUdrIZHFtUgmfSN4Gq9nHu3IkHMa1YDcN+s061Nfm+6pQ0mJy85YQDB0tZBCmls0Vuj22pLwDPmL92+Hvfwwlg==} - engines: {node: '>=16.0.0'} - - '@smithy/signature-v4@4.2.3': - resolution: {integrity: sha512-pPSQQ2v2vu9vc8iew7sszLd0O09I5TRc5zhY71KA+Ao0xYazIG+uLeHbTJfIWGO3BGVLiXjUr3EEeCcEQLjpWQ==} - engines: {node: '>=16.0.0'} - - '@smithy/smithy-client@3.4.4': - resolution: {integrity: sha512-dPGoJuSZqvirBq+yROapBcHHvFjChoAQT8YPWJ820aPHHiowBlB3RL1Q4kPT1hx0qKgJuf+HhyzKi5Gbof4fNA==} - engines: {node: '>=16.0.0'} - - '@smithy/types@3.7.1': - resolution: {integrity: sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==} - engines: {node: '>=16.0.0'} - - '@smithy/url-parser@3.0.10': - resolution: {integrity: sha512-j90NUalTSBR2NaZTuruEgavSdh8MLirf58LoGSk4AtQfyIymogIhgnGUU2Mga2bkMkpSoC9gxb74xBXL5afKAQ==} - - '@smithy/util-base64@3.0.0': - resolution: {integrity: sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-body-length-browser@3.0.0': - resolution: {integrity: sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==} - - '@smithy/util-body-length-node@3.0.0': - resolution: {integrity: sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==} - engines: {node: '>=16.0.0'} - - '@smithy/util-buffer-from@2.2.0': - resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} - engines: {node: '>=14.0.0'} - - '@smithy/util-buffer-from@3.0.0': - resolution: {integrity: sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==} - engines: {node: '>=16.0.0'} - - '@smithy/util-config-provider@3.0.0': - resolution: {integrity: sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-defaults-mode-browser@3.0.27': - resolution: {integrity: sha512-GV8NvPy1vAGp7u5iD/xNKUxCorE4nQzlyl057qRac+KwpH5zq8wVq6rE3lPPeuFLyQXofPN6JwxL1N9ojGapiQ==} - engines: {node: '>= 10.0.0'} - - '@smithy/util-defaults-mode-node@3.0.27': - resolution: {integrity: sha512-7+4wjWfZqZxZVJvDutO+i1GvL6bgOajEkop4FuR6wudFlqBiqwxw3HoH6M9NgeCd37km8ga8NPp2JacQEtAMPg==} - engines: {node: '>= 10.0.0'} - - '@smithy/util-endpoints@2.1.6': - resolution: {integrity: sha512-mFV1t3ndBh0yZOJgWxO9J/4cHZVn5UG1D8DeCc6/echfNkeEJWu9LD7mgGH5fHrEdR7LDoWw7PQO6QiGpHXhgA==} - engines: {node: '>=16.0.0'} - - '@smithy/util-hex-encoding@3.0.0': - resolution: {integrity: sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==} - engines: {node: '>=16.0.0'} - - '@smithy/util-middleware@3.0.10': - resolution: {integrity: sha512-eJO+/+RsrG2RpmY68jZdwQtnfsxjmPxzMlQpnHKjFPwrYqvlcT+fHdT+ZVwcjlWSrByOhGr9Ff2GG17efc192A==} - engines: {node: '>=16.0.0'} - - '@smithy/util-retry@3.0.10': - resolution: {integrity: sha512-1l4qatFp4PiU6j7UsbasUHL2VU023NRB/gfaa1M0rDqVrRN4g3mCArLRyH3OuktApA4ye+yjWQHjdziunw2eWA==} - engines: {node: '>=16.0.0'} - - '@smithy/util-stream@3.3.1': - resolution: {integrity: sha512-Ff68R5lJh2zj+AUTvbAU/4yx+6QPRzg7+pI7M1FbtQHcRIp7xvguxVsQBKyB3fwiOwhAKu0lnNyYBaQfSW6TNw==} - engines: {node: '>=16.0.0'} - - '@smithy/util-uri-escape@3.0.0': - resolution: {integrity: sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==} - engines: {node: '>=16.0.0'} - - '@smithy/util-utf8@2.3.0': - resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} - engines: {node: '>=14.0.0'} - - '@smithy/util-utf8@3.0.0': - resolution: {integrity: sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==} - engines: {node: '>=16.0.0'} - - '@smithy/util-waiter@3.1.9': - resolution: {integrity: sha512-/aMXPANhMOlMPjfPtSrDfPeVP8l56SJlz93xeiLmhLe5xvlXA5T3abZ2ilEsDEPeY9T/wnN/vNGn9wa1SbufWA==} - engines: {node: '>=16.0.0'} - - '@sqltools/formatter@1.2.5': - resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} - - '@tsconfig/node10@1.0.11': - resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} - - '@tsconfig/node12@1.0.11': - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - - '@tsconfig/node14@1.0.3': - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - - '@tsconfig/node16@1.0.4': - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.6.8': - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.20.6': - resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - - '@types/body-parser@1.19.5': - resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} - - '@types/connect@3.4.38': - resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - - '@types/cookiejar@2.1.5': - resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} - - '@types/eslint-scope@3.7.7': - resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} - - '@types/eslint@9.6.1': - resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} - - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - - '@types/express-serve-static-core@5.0.1': - resolution: {integrity: sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==} - - '@types/express@5.0.0': - resolution: {integrity: sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==} - - '@types/graceful-fs@4.1.9': - resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} - - '@types/http-errors@2.0.4': - resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} - - '@types/istanbul-lib-coverage@2.0.6': - resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} - - '@types/istanbul-lib-report@3.0.3': - resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} - - '@types/istanbul-reports@3.0.4': - resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} - - '@types/jest@29.5.2': - resolution: {integrity: sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg==} - - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - - '@types/jsonwebtoken@9.0.5': - resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==} - - '@types/methods@1.1.4': - resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} - - '@types/mime@1.3.5': - resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} - - '@types/multer@1.4.12': - resolution: {integrity: sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==} - - '@types/node@20.3.1': - resolution: {integrity: sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==} - - '@types/parse-json@4.0.2': - resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - - '@types/qs@6.9.17': - resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==} - - '@types/range-parser@1.2.7': - resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - - '@types/send@0.17.4': - resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} - - '@types/serve-static@1.15.7': - resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} - - '@types/stack-utils@2.0.3': - resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - - '@types/superagent@8.1.9': - resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==} - - '@types/supertest@6.0.0': - resolution: {integrity: sha512-j3/Z2avY+H3yn+xp/ef//QyqqE+dg3rWh14Ewi/QZs6uVK+oOs7lFRXtjp2YHAqHJZ4OFGNmCxZO5vd7AuG/Dg==} - - '@types/validator@13.12.2': - resolution: {integrity: sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==} - - '@types/yargs-parser@21.0.3': - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - - '@types/yargs@17.0.33': - resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} - - '@typescript-eslint/eslint-plugin@8.0.0': - resolution: {integrity: sha512-STIZdwEQRXAHvNUS6ILDf5z3u95Gc8jzywunxSNqX00OooIemaaNIA0vEgynJlycL5AjabYLLrIyHd4iazyvtg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/parser@8.0.0': - resolution: {integrity: sha512-pS1hdZ+vnrpDIxuFXYQpLTILglTjSYJ9MbetZctrUawogUsPdz31DIIRZ9+rab0LhYNTsk88w4fIzVheiTbWOQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/scope-manager@8.0.0': - resolution: {integrity: sha512-V0aa9Csx/ZWWv2IPgTfY7T4agYwJyILESu/PVqFtTFz9RIS823mAze+NbnBI8xiwdX3iqeQbcTYlvB04G9wyQw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/type-utils@8.0.0': - resolution: {integrity: sha512-mJAFP2mZLTBwAn5WI4PMakpywfWFH5nQZezUQdSKV23Pqo6o9iShQg1hP2+0hJJXP2LnZkWPphdIq4juYYwCeg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/types@8.0.0': - resolution: {integrity: sha512-wgdSGs9BTMWQ7ooeHtu5quddKKs5Z5dS+fHLbrQI+ID0XWJLODGMHRfhwImiHoeO2S5Wir2yXuadJN6/l4JRxw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@8.0.0': - resolution: {integrity: sha512-5b97WpKMX+Y43YKi4zVcCVLtK5F98dFls3Oxui8LbnmRsseKenbbDinmvxrWegKDMmlkIq/XHuyy0UGLtpCDKg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/utils@8.0.0': - resolution: {integrity: sha512-k/oS/A/3QeGLRvOWCg6/9rATJL5rec7/5s1YmdS0ZU6LHveJyGFwBvLhSRBv6i9xaj7etmosp+l+ViN1I9Aj/Q==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - - '@typescript-eslint/visitor-keys@8.0.0': - resolution: {integrity: sha512-oN0K4nkHuOyF3PVMyETbpP5zp6wfyOvm7tWhTMfoqxSSsPmJIh6JNASuZDlODE8eE+0EB9uar+6+vxr9DBTYOA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@webassemblyjs/ast@1.14.1': - resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} - - '@webassemblyjs/floating-point-hex-parser@1.13.2': - resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} - - '@webassemblyjs/helper-api-error@1.13.2': - resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} - - '@webassemblyjs/helper-buffer@1.14.1': - resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} - - '@webassemblyjs/helper-numbers@1.13.2': - resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} - - '@webassemblyjs/helper-wasm-bytecode@1.13.2': - resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} - - '@webassemblyjs/helper-wasm-section@1.14.1': - resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} - - '@webassemblyjs/ieee754@1.13.2': - resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} - - '@webassemblyjs/leb128@1.13.2': - resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} - - '@webassemblyjs/utf8@1.13.2': - resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} - - '@webassemblyjs/wasm-edit@1.14.1': - resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} - - '@webassemblyjs/wasm-gen@1.14.1': - resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} - - '@webassemblyjs/wasm-opt@1.14.1': - resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} - - '@webassemblyjs/wasm-parser@1.14.1': - resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} - - '@webassemblyjs/wast-printer@1.14.1': - resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} - - '@xtuc/ieee754@1.2.0': - resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} - - '@xtuc/long@4.2.2': - resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} - - accepts@1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} - - acorn-import-assertions@1.9.0: - resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} - deprecated: package has been renamed to acorn-import-attributes - peerDependencies: - acorn: ^8 - - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn-walk@8.3.4: - resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} - engines: {node: '>=0.4.0'} - - acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} - engines: {node: '>=0.4.0'} - hasBin: true - - ajv-formats@2.1.1: - resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - - ajv-keywords@3.5.2: - resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} - peerDependencies: - ajv: ^6.9.1 - - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - - ajv@8.12.0: - resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} - - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.1.0: - resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - - ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - - any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - app-root-path@3.1.0: - resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} - engines: {node: '>= 6.0.0'} - - append-field@1.0.0: - resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} - - arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - - argon2@0.41.1: - resolution: {integrity: sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==} - engines: {node: '>=16.17.0'} - - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - array-flatten@1.1.1: - resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - - array-timsort@1.0.3: - resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} - - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - - asap@2.0.6: - resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} - - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - - aws-ssl-profiles@1.1.2: - resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} - engines: {node: '>= 6.0.0'} - - axios@1.7.7: - resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} - - babel-jest@29.7.0: - resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.8.0 - - babel-plugin-istanbul@6.1.1: - resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} - engines: {node: '>=8'} - - babel-plugin-jest-hoist@29.6.3: - resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - babel-preset-current-node-syntax@1.1.0: - resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} - peerDependencies: - '@babel/core': ^7.0.0 - - babel-preset-jest@29.6.3: - resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.0.0 - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - - body-parser@1.20.1: - resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - - body-parser@1.20.2: - resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - - bowser@2.11.0: - resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} - - brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - browserslist@4.24.2: - resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - bs-logger@0.2.6: - resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} - engines: {node: '>= 6'} - - bser@2.1.1: - resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} - - buffer-equal-constant-time@1.0.1: - resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - - busboy@1.6.0: - resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} - engines: {node: '>=10.16.0'} - - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - - call-bind@1.0.7: - resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} - engines: {node: '>= 0.4'} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - - caniuse-lite@1.0.30001679: - resolution: {integrity: sha512-j2YqID/YwpLnKzCmBOS4tlZdWprXm3ZmQLBH9ZBXFOhoxLA46fwyBvx6toCBWBmnuwUY/qB3kEU6gFx8qgCroA==} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - char-regex@1.0.2: - resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} - engines: {node: '>=10'} - - chardet@0.7.0: - resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - - chokidar@3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} - engines: {node: '>= 8.10.0'} - - chrome-trace-event@1.0.4: - resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} - engines: {node: '>=6.0'} - - ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} - - cjs-module-lexer@1.4.1: - resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} - - class-transformer@0.5.1: - resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} - - class-validator@0.14.1: - resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==} - - cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} - - cli-highlight@2.1.11: - resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} - engines: {node: '>=8.0.0', npm: '>=5.0.0'} - hasBin: true - - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} - - cli-table3@0.6.3: - resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==} - engines: {node: 10.* || >= 12.*} - - cli-width@3.0.0: - resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} - engines: {node: '>= 10'} - - cliui@7.0.4: - resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} - - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - - clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} - - co@4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - - collect-v8-coverage@1.0.2: - resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - - commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - - comment-json@4.2.3: - resolution: {integrity: sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==} - engines: {node: '>= 6'} - - component-emitter@1.3.1: - resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - concat-stream@1.6.2: - resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} - engines: {'0': node >= 0.8} - - consola@2.15.3: - resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} - - consola@3.2.3: - resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} - engines: {node: ^14.18.0 || >=16.10.0} - - content-disposition@0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - cookie-signature@1.0.6: - resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - - cookie@0.5.0: - resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} - engines: {node: '>= 0.6'} - - cookiejar@2.1.4: - resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} - - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - - cors@2.8.5: - resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} - engines: {node: '>= 0.10'} - - cosmiconfig@7.1.0: - resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} - engines: {node: '>=10'} - - create-jest@29.7.0: - resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - - create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - - cross-spawn@7.0.5: - resolution: {integrity: sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==} - engines: {node: '>= 8'} - - dayjs@1.11.13: - resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} - - debug@2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - debug@4.3.7: - resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - dedent@1.5.3: - resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - - deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - - defaults@1.0.4: - resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} - - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - - denque@2.1.0: - resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} - engines: {node: '>=0.10'} - - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - - destr@2.0.3: - resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} - - destroy@1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - - detect-newline@3.1.0: - resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} - engines: {node: '>=8'} - - dezalgo@1.0.4: - resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} - - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} - - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - - dotenv-expand@10.0.0: - resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} - engines: {node: '>=12'} - - dotenv@16.4.5: - resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} - engines: {node: '>=12'} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - ebec@1.1.1: - resolution: {integrity: sha512-JZ1vcvPQtR+8LGbZmbjG21IxLQq/v47iheJqn2F6yB2CgnGfn8ZVg3myHrf3buIZS8UCwQK0jOSIb3oHX7aH8g==} - - ebec@2.3.0: - resolution: {integrity: sha512-bt+0tSL7223VU3PSVi0vtNLZ8pO1AfWolcPPMk2a/a5H+o/ZU9ky0n3A0zhrR4qzJTN61uPsGIO4ShhOukdzxA==} - - ecdsa-sig-formatter@1.0.11: - resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} - - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - - electron-to-chromium@1.5.55: - resolution: {integrity: sha512-6maZ2ASDOTBtjt9FhqYPRnbvKU5tjG0IN9SztUOWYw2AzNDNpKJYLJmlK0/En4Hs/aiWnB+JZ+gW19PIGszgKg==} - - emittery@0.13.1: - resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} - engines: {node: '>=12'} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - encodeurl@1.0.2: - resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} - engines: {node: '>= 0.8'} - - end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - - enhanced-resolve@5.17.1: - resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} - engines: {node: '>=10.13.0'} - - enquirer@2.4.1: - resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} - engines: {node: '>=8.6'} - - envix@1.5.0: - resolution: {integrity: sha512-IOxTKT+tffjxgvX2O5nq6enbkv6kBQ/QdMy18bZWo0P0rKPvsRp2/EypIPwTvJfnmk3VdOlq/KcRSZCswefM/w==} - engines: {node: '>=18.0.0'} - - error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - - es-define-property@1.0.0: - resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-module-lexer@1.5.4: - resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - - escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - eslint-config-prettier@9.0.0: - resolution: {integrity: sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - - eslint-plugin-prettier@5.0.0: - resolution: {integrity: sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - '@types/eslint': '>=8.0.0' - eslint: '>=8.0.0' - eslint-config-prettier: '*' - prettier: '>=3.0.0' - peerDependenciesMeta: - '@types/eslint': - optional: true - eslint-config-prettier: - optional: true - - eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} - - eslint-scope@6.0.0: - resolution: {integrity: sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-utils@3.0.0: - resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} - engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} - peerDependencies: - eslint: '>=5' - - eslint-visitor-keys@2.1.0: - resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} - engines: {node: '>=10'} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint@8.0.0: - resolution: {integrity: sha512-03spzPzMAO4pElm44m60Nj08nYonPGQXmw6Ceai/S4QK82IgwWO1EXx1s9namKzVlbVu3Jf81hb+N+8+v21/HQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true - - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - - execa@4.1.0: - resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} - engines: {node: '>=10'} - - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - - exit@0.1.2: - resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} - engines: {node: '>= 0.8.0'} - - expect@29.7.0: - resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - express@4.18.2: - resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} - engines: {node: '>= 0.10.0'} - - external-editor@3.1.0: - resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} - engines: {node: '>=4'} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} - engines: {node: '>=8.6.0'} - - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - - fast-safe-stringify@2.1.1: - resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - - fast-xml-parser@4.4.1: - resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} - hasBin: true - - fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - - fb-watchman@2.0.2: - resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} - - figures@3.2.0: - resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} - engines: {node: '>=8'} - - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - finalhandler@1.2.0: - resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} - engines: {node: '>= 0.8'} - - find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} - - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} - - flat@5.0.2: - resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} - hasBin: true - - flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - - follow-redirects@1.15.9: - resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - - foreground-child@3.3.0: - resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} - engines: {node: '>=14'} - - fork-ts-checker-webpack-plugin@8.0.0: - resolution: {integrity: sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==} - engines: {node: '>=12.13.0', yarn: '>=1.0.0'} - peerDependencies: - typescript: '>3.6.0' - webpack: ^5.11.0 - - form-data@4.0.1: - resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} - engines: {node: '>= 6'} - - formidable@3.5.2: - resolution: {integrity: sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==} - - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - - fresh@0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} - engines: {node: '>= 0.6'} - - fs-extra@10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} - engines: {node: '>=12'} - - fs-monkey@1.0.6: - resolution: {integrity: sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==} - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - functional-red-black-tree@1.0.1: - resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} - - generate-function@2.3.1: - resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - get-intrinsic@1.2.4: - resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} - engines: {node: '>= 0.4'} - - get-package-type@0.1.0: - resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} - engines: {node: '>=8.0.0'} - - get-stream@5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} - - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - - glob-to-regexp@0.4.1: - resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} - hasBin: true - - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - - glob@9.3.5: - resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} - engines: {node: '>=16 || 14 >=14.17'} - - globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} - - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - - gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - has-own-prop@2.0.0: - resolution: {integrity: sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==} - engines: {node: '>=8'} - - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - - has-proto@1.0.3: - resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} - engines: {node: '>= 0.4'} - - has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - hexoid@2.0.0: - resolution: {integrity: sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==} - engines: {node: '>=8'} - - highlight.js@10.7.3: - resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} - - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - - http-errors@2.0.0: - resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} - engines: {node: '>= 0.8'} - - human-signals@1.1.1: - resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} - engines: {node: '>=8.12.0'} - - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} - - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - ignore@4.0.6: - resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} - engines: {node: '>= 4'} - - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - - import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - - import-local@3.2.0: - resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} - engines: {node: '>=8'} - hasBin: true - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - inquirer@8.2.4: - resolution: {integrity: sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==} - engines: {node: '>=12.0.0'} - - inquirer@8.2.5: - resolution: {integrity: sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==} - engines: {node: '>=12.0.0'} - - interpret@1.4.0: - resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} - engines: {node: '>= 0.10'} - - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-core-module@2.15.1: - resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} - engines: {node: '>= 0.4'} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-generator-fn@2.1.0: - resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} - engines: {node: '>=6'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-interactive@1.0.0: - resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} - engines: {node: '>=8'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-property@1.0.2: - resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - - is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - - istanbul-lib-instrument@5.2.1: - resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} - engines: {node: '>=8'} - - istanbul-lib-instrument@6.0.3: - resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} - engines: {node: '>=10'} - - istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} - - istanbul-lib-source-maps@4.0.1: - resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} - engines: {node: '>=10'} - - istanbul-reports@3.1.7: - resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} - engines: {node: '>=8'} - - iterare@1.2.1: - resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} - engines: {node: '>=6'} - - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - - jest-changed-files@29.7.0: - resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-circus@29.7.0: - resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-cli@29.7.0: - resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - jest-config@29.7.0: - resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true - - jest-diff@29.7.0: - resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-docblock@29.7.0: - resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-each@29.7.0: - resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-environment-node@29.7.0: - resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-get-type@29.6.3: - resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-haste-map@29.7.0: - resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-leak-detector@29.7.0: - resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-matcher-utils@29.7.0: - resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-message-util@29.7.0: - resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-mock@29.7.0: - resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-pnp-resolver@1.2.3: - resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} - engines: {node: '>=6'} - peerDependencies: - jest-resolve: '*' - peerDependenciesMeta: - jest-resolve: - optional: true - - jest-regex-util@29.6.3: - resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-resolve-dependencies@29.7.0: - resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-resolve@29.7.0: - resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-runner@29.7.0: - resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-runtime@29.7.0: - resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-snapshot@29.7.0: - resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-util@29.7.0: - resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-validate@29.7.0: - resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-watcher@29.7.0: - resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest-worker@27.5.1: - resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} - engines: {node: '>= 10.13.0'} - - jest-worker@29.7.0: - resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - jest@29.5.0: - resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - - jiti@2.4.0: - resolution: {integrity: sha512-H5UpaUI+aHOqZXlYOaFP/8AzKsg+guWu+Pr3Y8i7+Y3zr1aXAvCvTAQ1RxSc6oVD8R8c7brgNtTVP91E7upH/g==} - hasBin: true - - joi@17.13.3: - resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true - - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - - jsesc@3.0.2: - resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} - engines: {node: '>=6'} - hasBin: true - - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - jsonc-parser@3.2.0: - resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} - - jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - - jsonwebtoken@9.0.2: - resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} - engines: {node: '>=12', npm: '>=6'} - - jwa@1.4.1: - resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} - - jws@3.2.2: - resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} - - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - - leven@3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} - engines: {node: '>=6'} - - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - - libphonenumber-js@1.11.14: - resolution: {integrity: sha512-sexvAfwcW1Lqws4zFp8heAtAEXbEDnvkYCEGzvOoMgZR7JhXo/IkE9MkkGACgBed5fWqh3ShBGnJBdDnU9N8EQ==} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - loader-runner@4.3.0: - resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} - engines: {node: '>=6.11.5'} - - locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - - locter@2.1.5: - resolution: {integrity: sha512-eI57PuVxigQ0GBscGIIFGPB467E5zKODHD3XGuknzLvf7HdnvRw3GdZVGj1J8XKsKOYovZQesX/oOdTwbdjwuQ==} - - lodash.includes@4.3.0: - resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} - - lodash.isboolean@3.0.3: - resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} - - lodash.isinteger@4.0.4: - resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} - - lodash.isnumber@3.0.3: - resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} - - lodash.isplainobject@4.0.6: - resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - - lodash.isstring@4.0.1: - resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} - - lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - lodash.once@4.1.1: - resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} - - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - - long@5.2.3: - resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} - - lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - lru-cache@7.18.3: - resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} - engines: {node: '>=12'} - - lru.min@1.1.1: - resolution: {integrity: sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==} - engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} - - macos-release@2.5.1: - resolution: {integrity: sha512-DXqXhEM7gW59OjZO8NIjBCz9AQ1BEMrfiOAl4AYByHCtVHRF4KoGNO8mqQeM8lRCtQe/UnJ4imO/d2HdkKsd+A==} - engines: {node: '>=6'} - - magic-string@0.30.0: - resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==} - engines: {node: '>=12'} - - make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} - - make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - - makeerror@1.0.12: - resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} - - media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} - - memfs@3.5.3: - resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} - engines: {node: '>= 4.0.0'} - - merge-descriptors@1.0.1: - resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - methods@1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} - engines: {node: '>= 0.6'} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - mime@1.6.0: - resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} - engines: {node: '>=4'} - hasBin: true - - mime@2.6.0: - resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} - engines: {node: '>=4.0.0'} - hasBin: true - - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minimatch@8.0.4: - resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==} - engines: {node: '>=16 || 14 >=14.17'} - - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass@4.2.8: - resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} - engines: {node: '>=8'} - - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} - engines: {node: '>=16 || 14 >=14.17'} - - mkdirp@0.5.6: - resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} - hasBin: true - - mkdirp@2.1.6: - resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} - engines: {node: '>=10'} - hasBin: true - - ms@2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - multer@1.4.4-lts.1: - resolution: {integrity: sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==} - engines: {node: '>= 6.0.0'} - - mute-stream@0.0.8: - resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} - - mysql2@3.11.4: - resolution: {integrity: sha512-Z2o3tY4Z8EvSRDwknaC40MdZ3+m0sKbpnXrShQLdxPrAvcNli7jLrD2Zd2IzsRMw4eK9Yle500FDmlkIqp+krg==} - engines: {node: '>= 8.0'} - - mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - - named-placeholders@1.1.3: - resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} - engines: {node: '>=12.0.0'} - - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} - - neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - - no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} - - node-abort-controller@3.1.1: - resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} - - node-addon-api@8.2.2: - resolution: {integrity: sha512-9emqXAKhVoNrQ792nLI/wpzPpJ/bj/YXxW0CvAau1+RdGBcCRF1Dmz7719zgVsQNrzHl9Tzn3ImZ4qWFarWL0A==} - engines: {node: ^18 || ^20 || >= 21} - - node-emoji@1.11.0: - resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} - - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - - node-gyp-build@4.8.2: - resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==} - hasBin: true - - node-int64@0.4.0: - resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - - node-releases@2.0.18: - resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.3: - resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==} - engines: {node: '>= 0.4'} - - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - - ora@5.4.1: - resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} - engines: {node: '>=10'} - - os-name@4.0.1: - resolution: {integrity: sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw==} - engines: {node: '>=10'} - - os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} - - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - - parse5-htmlparser2-tree-adapter@6.0.1: - resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} - - parse5@5.1.1: - resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} - - parse5@6.0.1: - resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} - - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - - pascal-case@3.1.2: - resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - - path-to-regexp@0.1.7: - resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} - - path-to-regexp@3.2.0: - resolution: {integrity: sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==} - - path-to-regexp@3.3.0: - resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} - - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - - pg-cloudflare@1.1.1: - resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} - - pg-connection-string@2.7.0: - resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} - - pg-int8@1.0.1: - resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} - engines: {node: '>=4.0.0'} - - pg-pool@3.7.0: - resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==} - peerDependencies: - pg: '>=8.0' - - pg-protocol@1.7.0: - resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} - - pg-types@2.2.0: - resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} - engines: {node: '>=4'} - - pg@8.13.1: - resolution: {integrity: sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==} - engines: {node: '>= 8.0.0'} - peerDependencies: - pg-native: '>=3.0.1' - peerDependenciesMeta: - pg-native: - optional: true - - pgpass@1.0.5: - resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} - engines: {node: '>= 6'} - - pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} - - pluralize@8.0.0: - resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} - engines: {node: '>=4'} - - postgres-array@2.0.0: - resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} - engines: {node: '>=4'} - - postgres-bytea@1.0.0: - resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} - engines: {node: '>=0.10.0'} - - postgres-date@1.0.7: - resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} - engines: {node: '>=0.10.0'} - - postgres-interval@1.2.0: - resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} - engines: {node: '>=0.10.0'} - - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prettier-linter-helpers@1.0.0: - resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} - engines: {node: '>=6.0.0'} - - prettier@3.0.0: - resolution: {integrity: sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==} - engines: {node: '>=14'} - hasBin: true - - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - - progress@2.0.3: - resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} - engines: {node: '>=0.4.0'} - - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} - - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - - pump@3.0.2: - resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - pure-rand@6.1.0: - resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - - qs@6.11.0: - resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} - engines: {node: '>=0.6'} - - qs@6.13.0: - resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} - engines: {node: '>=0.6'} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - - rapiq@0.9.0: - resolution: {integrity: sha512-k4oT4RarFBrlLMJ49xUTeQpa/us0uU4I70D/UEnK3FWQ4GENzei01rEQAmvPKAIzACo4NMW+YcYJ7EVfSa7EFg==} - - raw-body@2.5.1: - resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} - engines: {node: '>= 0.8'} - - raw-body@2.5.2: - resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} - engines: {node: '>= 0.8'} - - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - rechoir@0.6.2: - resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} - engines: {node: '>= 0.10'} - - reflect-metadata@0.2.0: - resolution: {integrity: sha512-vUN0wuk3MuhSVMfU/ImnPQAK8QZcXJ339DtVsP3jDscxCe6dT+PsOe3J1BYS9Ec2Fd4oC6ry6bCBebzTya0IYw==} - deprecated: This version has a critical bug in fallback handling. Please upgrade to reflect-metadata@0.2.2 or newer. - - reflect-metadata@0.2.2: - resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} - - regexpp@3.2.0: - resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} - engines: {node: '>=8'} - - repeat-string@1.6.1: - resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} - engines: {node: '>=0.10'} - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - - resolve-cwd@3.0.0: - resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} - engines: {node: '>=8'} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - - resolve.exports@2.0.2: - resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} - engines: {node: '>=10'} - - resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} - hasBin: true - - restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} - - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - - rimraf@4.4.1: - resolution: {integrity: sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==} - engines: {node: '>=14'} - hasBin: true - - run-async@2.4.1: - resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} - engines: {node: '>=0.12.0'} - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} - - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - schema-utils@3.3.0: - resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} - engines: {node: '>= 10.13.0'} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} - engines: {node: '>=10'} - hasBin: true - - send@0.18.0: - resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} - engines: {node: '>= 0.8.0'} - - seq-queue@0.0.5: - resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} - - serialize-javascript@6.0.2: - resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - - serve-static@1.15.0: - resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} - engines: {node: '>= 0.8.0'} - - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - - sha.js@2.4.11: - resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} - hasBin: true - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - shelljs@0.8.5: - resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} - engines: {node: '>=4'} - hasBin: true - - side-channel@1.0.6: - resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} - engines: {node: '>= 0.4'} - - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - smob@1.5.0: - resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} - - source-map-support@0.5.13: - resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} - - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - source-map@0.7.4: - resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} - engines: {node: '>= 8'} - - split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - - sqlstring@2.3.3: - resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} - engines: {node: '>= 0.6'} - - stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} - - statuses@2.0.1: - resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} - engines: {node: '>= 0.8'} - - std-env@3.8.0: - resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} - - streamsearch@1.1.0: - resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} - engines: {node: '>=10.0.0'} - - string-length@4.0.2: - resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} - engines: {node: '>=10'} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - - strip-bom@4.0.0: - resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} - engines: {node: '>=8'} - - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - strnum@1.0.5: - resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} - - superagent@9.0.2: - resolution: {integrity: sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==} - engines: {node: '>=14.18.0'} - - supertest@7.0.0: - resolution: {integrity: sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==} - engines: {node: '>=14.18.0'} - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - swagger-ui-dist@5.18.2: - resolution: {integrity: sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw==} - - symbol-observable@4.0.0: - resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} - engines: {node: '>=0.10'} - - synckit@0.8.8: - resolution: {integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==} - engines: {node: ^14.18.0 || >=16.0.0} - - tapable@2.2.1: - resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} - engines: {node: '>=6'} - - terser-webpack-plugin@5.3.10: - resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} - engines: {node: '>= 10.13.0'} - peerDependencies: - '@swc/core': '*' - esbuild: '*' - uglify-js: '*' - webpack: ^5.1.0 - peerDependenciesMeta: - '@swc/core': - optional: true - esbuild: - optional: true - uglify-js: - optional: true - - terser@5.36.0: - resolution: {integrity: sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==} - engines: {node: '>=10'} - hasBin: true - - test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} - - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - - thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - - thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - - tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} - - tmpl@1.0.5: - resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - - ts-api-utils@1.4.0: - resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - - ts-jest@29.1.0: - resolution: {integrity: sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - '@babel/core': '>=7.0.0-beta.0 <8' - '@jest/types': ^29.0.0 - babel-jest: ^29.0.0 - esbuild: '*' - jest: ^29.0.0 - typescript: '>=4.3 <6' - peerDependenciesMeta: - '@babel/core': - optional: true - '@jest/types': - optional: true - babel-jest: - optional: true - esbuild: - optional: true - - ts-loader@9.4.3: - resolution: {integrity: sha512-n3hBnm6ozJYzwiwt5YRiJZkzktftRpMiBApHaJPoWLA+qetQBAXkHqCLM6nwSdRDimqVtA5ocIkcTRLMTt7yzA==} - engines: {node: '>=12.0.0'} - peerDependencies: - typescript: '*' - webpack: ^5.0.0 - - ts-node@10.9.1: - resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - - tsconfig-paths-webpack-plugin@4.0.1: - resolution: {integrity: sha512-m5//KzLoKmqu2MVix+dgLKq70MnFi8YL8sdzQZ6DblmCdfuq/y3OqvJd5vMndg2KEVCOeNz8Es4WVZhYInteLw==} - engines: {node: '>=10.13.0'} - - tsconfig-paths@4.2.0: - resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} - engines: {node: '>=6'} - - tslib@2.5.3: - resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - - type-is@1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} - - typedarray@0.0.6: - resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - - typeorm-extension@3.6.3: - resolution: {integrity: sha512-AE+8KqBphlBdVz5JS77o6LZzzi+b+YFFt8So4Qu/KRo/iynAwekrx98Oxuu3FAYNm6DUKDcubOBMZsJeiRvHkA==} - engines: {node: '>=14.0.0'} - hasBin: true - peerDependencies: - typeorm: ~0.3.0 - - typeorm@0.3.20: - resolution: {integrity: sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q==} - engines: {node: '>=16.13.0'} - hasBin: true - peerDependencies: - '@google-cloud/spanner': ^5.18.0 - '@sap/hana-client': ^2.12.25 - better-sqlite3: ^7.1.2 || ^8.0.0 || ^9.0.0 - hdb-pool: ^0.1.6 - ioredis: ^5.0.4 - mongodb: ^5.8.0 - mssql: ^9.1.1 || ^10.0.1 - mysql2: ^2.2.5 || ^3.0.1 - oracledb: ^6.3.0 - pg: ^8.5.1 - pg-native: ^3.0.0 - pg-query-stream: ^4.0.0 - redis: ^3.1.1 || ^4.0.0 - sql.js: ^1.4.0 - sqlite3: ^5.0.3 - ts-node: ^10.7.0 - typeorm-aurora-data-api-driver: ^2.0.0 - peerDependenciesMeta: - '@google-cloud/spanner': - optional: true - '@sap/hana-client': - optional: true - better-sqlite3: - optional: true - hdb-pool: - optional: true - ioredis: - optional: true - mongodb: - optional: true - mssql: - optional: true - mysql2: - optional: true - oracledb: - optional: true - pg: - optional: true - pg-native: - optional: true - pg-query-stream: - optional: true - redis: - optional: true - sql.js: - optional: true - sqlite3: - optional: true - ts-node: - optional: true - typeorm-aurora-data-api-driver: - optional: true - - typescript@5.1.3: - resolution: {integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==} - engines: {node: '>=14.17'} - hasBin: true - - uid@2.0.2: - resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} - engines: {node: '>=8'} - - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - - update-browserslist-db@1.1.1: - resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - utils-merge@1.0.1: - resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} - engines: {node: '>= 0.4.0'} - - uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - hasBin: true - - v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - - v8-compile-cache@2.4.0: - resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==} - - v8-to-istanbul@9.3.0: - resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} - engines: {node: '>=10.12.0'} - - validator@13.12.0: - resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} - engines: {node: '>= 0.10'} - - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - - walker@1.0.8: - resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} - - watchpack@2.4.2: - resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==} - engines: {node: '>=10.13.0'} - - wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} - - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - webpack-node-externals@3.0.0: - resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} - engines: {node: '>=6'} - - webpack-sources@3.2.3: - resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} - engines: {node: '>=10.13.0'} - - webpack@5.87.0: - resolution: {integrity: sha512-GOu1tNbQ7p1bDEoFRs2YPcfyGs8xq52yyPBZ3m2VGnXGtV9MxjrkABHm4V9Ia280OefsSLzvbVoXcfLxjKY/Iw==} - engines: {node: '>=10.13.0'} - hasBin: true - peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - windows-release@4.0.0: - resolution: {integrity: sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg==} - engines: {node: '>=10'} - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - write-file-atomic@4.0.2: - resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} - - yaml@2.6.0: - resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==} - engines: {node: '>= 14'} - hasBin: true - - yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} - engines: {node: '>=10'} - - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - - yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} +dependencies: + '@aws-sdk/client-s3': + specifier: ^3.693.0 + version: 3.693.0 + '@nestjs/axios': + specifier: ^3.1.2 + version: 3.1.2(@nestjs/common@10.0.0)(axios@1.7.7)(rxjs@7.8.1) + '@nestjs/class-validator': + specifier: 0.13.1 + version: 0.13.1 + '@nestjs/common': + specifier: ^10.0.0 + version: 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/config': + specifier: ^3.3.0 + version: 3.3.0(@nestjs/common@10.0.0)(rxjs@7.8.1) + '@nestjs/core': + specifier: ^10.0.0 + version: 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/jwt': + specifier: ^10.2.0 + version: 10.2.0(@nestjs/common@10.0.0) + '@nestjs/mapped-types': + specifier: ^2.0.6 + version: 2.0.6(@nestjs/common@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) + '@nestjs/platform-express': + specifier: ^10.0.0 + version: 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0) + '@nestjs/swagger': + specifier: ^8.0.5 + version: 8.0.5(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) + '@nestjs/typeorm': + specifier: ^10.0.2 + version: 10.0.2(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)(typeorm@0.3.20) + '@smithy/types': + specifier: ^3.7.1 + version: 3.7.1 + argon2: + specifier: ^0.41.1 + version: 0.41.1 + axios: + specifier: ^1.7.7 + version: 1.7.7 + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.1 + version: 0.14.1 + dotenv: + specifier: ^16.4.5 + version: 16.4.5 + joi: + specifier: ^17.13.3 + version: 17.13.3 + mysql2: + specifier: ^3.11.4 + version: 3.11.4 + pg: + specifier: ^8.13.1 + version: 8.13.1 + reflect-metadata: + specifier: ^0.2.0 + version: 0.2.0 + rxjs: + specifier: ^7.8.1 + version: 7.8.1 + typeorm: + specifier: ^0.3.20 + version: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1) + typeorm-extension: + specifier: ^3.6.3 + version: 3.6.3(typeorm@0.3.20) + +devDependencies: + '@nestjs/cli': + specifier: ^10.0.0 + version: 10.0.0 + '@nestjs/schematics': + specifier: ^10.0.0 + version: 10.0.0(chokidar@3.5.3)(typescript@5.1.3) + '@nestjs/testing': + specifier: ^10.0.0 + version: 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0) + '@types/express': + specifier: ^5.0.0 + version: 5.0.0 + '@types/jest': + specifier: ^29.5.2 + version: 29.5.2 + '@types/multer': + specifier: ^1.4.12 + version: 1.4.12 + '@types/node': + specifier: ^20.3.1 + version: 20.3.1 + '@types/supertest': + specifier: ^6.0.0 + version: 6.0.0 + '@typescript-eslint/eslint-plugin': + specifier: ^8.0.0 + version: 8.0.0(@typescript-eslint/parser@8.0.0)(eslint@8.0.0)(typescript@5.1.3) + '@typescript-eslint/parser': + specifier: ^8.0.0 + version: 8.0.0(eslint@8.0.0)(typescript@5.1.3) + eslint: + specifier: ^8.0.0 + version: 8.0.0 + eslint-config-prettier: + specifier: ^9.0.0 + version: 9.0.0(eslint@8.0.0) + eslint-plugin-prettier: + specifier: ^5.0.0 + version: 5.0.0(eslint-config-prettier@9.0.0)(eslint@8.0.0)(prettier@3.0.0) + jest: + specifier: ^29.5.0 + version: 29.5.0(@types/node@20.3.1)(ts-node@10.9.1) + prettier: + specifier: ^3.0.0 + version: 3.0.0 + source-map-support: + specifier: ^0.5.21 + version: 0.5.21 + supertest: + specifier: ^7.0.0 + version: 7.0.0 + ts-jest: + specifier: ^29.1.0 + version: 29.1.0(@babel/core@7.26.0)(jest@29.5.0)(typescript@5.1.3) + ts-loader: + specifier: ^9.4.3 + version: 9.4.3(typescript@5.1.3)(webpack@5.96.1) + ts-node: + specifier: ^10.9.1 + version: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) + tsconfig-paths: + specifier: ^4.2.0 + version: 4.2.0 + typescript: + specifier: ^5.1.3 + version: 5.1.3 - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - -snapshots: +packages: - '@ampproject/remapping@2.3.0': + /@ampproject/remapping@2.3.0: + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} dependencies: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 + dev: true - '@angular-devkit/core@16.1.0(chokidar@3.5.3)': + /@angular-devkit/core@16.1.0(chokidar@3.5.3): + resolution: {integrity: sha512-mrWpuDvttmhrCGcLc68RIXKtTzUhkBTsE5ZZFZNO1+FSC+vO/ZpyCpPd6C+6coM68NfXYjHlms5XF6KbxeGn/Q==} + engines: {node: ^16.14.0 || >=18.10.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + peerDependencies: + chokidar: ^3.5.2 + peerDependenciesMeta: + chokidar: + optional: true dependencies: ajv: 8.12.0 ajv-formats: 2.1.1(ajv@8.12.0) + chokidar: 3.5.3 jsonc-parser: 3.2.0 rxjs: 7.8.1 source-map: 0.7.4 - optionalDependencies: - chokidar: 3.5.3 + dev: true - '@angular-devkit/schematics-cli@16.1.0(chokidar@3.5.3)': + /@angular-devkit/schematics-cli@16.1.0(chokidar@3.5.3): + resolution: {integrity: sha512-siBpRDmMMV7NB+NvaDHeJ4doHoSkFwIywwFj8GXnBCtobyxrBl1EyG1cKK+FHRydYtyYIk8FEoOpJA9oE9S2hg==} + engines: {node: ^16.14.0 || >=18.10.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + hasBin: true dependencies: '@angular-devkit/core': 16.1.0(chokidar@3.5.3) '@angular-devkit/schematics': 16.1.0(chokidar@3.5.3) @@ -3679,8 +186,11 @@ snapshots: yargs-parser: 21.1.1 transitivePeerDependencies: - chokidar + dev: true - '@angular-devkit/schematics@16.1.0(chokidar@3.5.3)': + /@angular-devkit/schematics@16.1.0(chokidar@3.5.3): + resolution: {integrity: sha512-LM35PH9DT3eQRSZgrkk2bx1ZQjjVh8BCByTlr37/c+FnF9mNbeBsa1YkxrlsN/CwO+045OwEwRHnkM9Zcx0U/A==} + engines: {node: ^16.14.0 || >=18.10.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} dependencies: '@angular-devkit/core': 16.1.0(chokidar@3.5.3) jsonc-parser: 3.2.0 @@ -3689,20 +199,27 @@ snapshots: rxjs: 7.8.1 transitivePeerDependencies: - chokidar + dev: true - '@aws-crypto/crc32@5.2.0': + /@aws-crypto/crc32@5.2.0: + resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==} + engines: {node: '>=16.0.0'} dependencies: '@aws-crypto/util': 5.2.0 '@aws-sdk/types': 3.692.0 tslib: 2.8.1 + dev: false - '@aws-crypto/crc32c@5.2.0': + /@aws-crypto/crc32c@5.2.0: + resolution: {integrity: sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==} dependencies: '@aws-crypto/util': 5.2.0 '@aws-sdk/types': 3.692.0 tslib: 2.8.1 + dev: false - '@aws-crypto/sha1-browser@5.2.0': + /@aws-crypto/sha1-browser@5.2.0: + resolution: {integrity: sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==} dependencies: '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 @@ -3710,8 +227,10 @@ snapshots: '@aws-sdk/util-locate-window': 3.693.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 + dev: false - '@aws-crypto/sha256-browser@5.2.0': + /@aws-crypto/sha256-browser@5.2.0: + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} dependencies: '@aws-crypto/sha256-js': 5.2.0 '@aws-crypto/supports-web-crypto': 5.2.0 @@ -3720,24 +239,34 @@ snapshots: '@aws-sdk/util-locate-window': 3.693.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 + dev: false - '@aws-crypto/sha256-js@5.2.0': + /@aws-crypto/sha256-js@5.2.0: + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} dependencies: '@aws-crypto/util': 5.2.0 '@aws-sdk/types': 3.692.0 tslib: 2.8.1 + dev: false - '@aws-crypto/supports-web-crypto@5.2.0': + /@aws-crypto/supports-web-crypto@5.2.0: + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} dependencies: tslib: 2.8.1 + dev: false - '@aws-crypto/util@5.2.0': + /@aws-crypto/util@5.2.0: + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 + dev: false - '@aws-sdk/client-s3@3.693.0': + /@aws-sdk/client-s3@3.693.0: + resolution: {integrity: sha512-vgGI2e0Q6pzyhqfrSysi+sk/i+Nl+lMon67oqj/57RcCw9daL1/inpS+ADuwHpiPWkrg+U0bOXnmHjkLeTslJg==} + engines: {node: '>=16.0.0'} dependencies: '@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0 @@ -3745,7 +274,7 @@ snapshots: '@aws-sdk/client-sso-oidc': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/client-sts': 3.693.0 '@aws-sdk/core': 3.693.0 - '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) '@aws-sdk/middleware-bucket-endpoint': 3.693.0 '@aws-sdk/middleware-expect-continue': 3.693.0 '@aws-sdk/middleware-flexible-checksums': 3.693.0 @@ -3799,14 +328,19 @@ snapshots: tslib: 2.8.1 transitivePeerDependencies: - aws-crt + dev: false - '@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)': + /@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0): + resolution: {integrity: sha512-UEDbYlYtK/e86OOMyFR4zEPyenIxDzO2DRdz3fwVW7RzZ94wfmSwBh/8skzPTuY1G7sI064cjHW0b0QG01Sdtg==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.693.0 dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 '@aws-sdk/client-sts': 3.693.0 '@aws-sdk/core': 3.693.0 - '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) '@aws-sdk/middleware-host-header': 3.693.0 '@aws-sdk/middleware-logger': 3.693.0 '@aws-sdk/middleware-recursion-detection': 3.693.0 @@ -3844,8 +378,11 @@ snapshots: tslib: 2.8.1 transitivePeerDependencies: - aws-crt + dev: false - '@aws-sdk/client-sso@3.693.0': + /@aws-sdk/client-sso@3.693.0: + resolution: {integrity: sha512-QEynrBC26x6TG9ZMzApR/kZ3lmt4lEIs2D+cHuDxt6fDGzahBUsQFBwJqhizzsM97JJI5YvmJhmihoYjdSSaXA==} + engines: {node: '>=16.0.0'} dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 @@ -3887,14 +424,17 @@ snapshots: tslib: 2.8.1 transitivePeerDependencies: - aws-crt + dev: false - '@aws-sdk/client-sts@3.693.0': + /@aws-sdk/client-sts@3.693.0: + resolution: {integrity: sha512-4S2y7VEtvdnjJX4JPl4kDQlslxXEZFnC50/UXVUYSt/AMc5A/GgspFNA5FVz4E3Gwpfobbf23hR2NBF8AGvYoQ==} + engines: {node: '>=16.0.0'} dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 '@aws-sdk/client-sso-oidc': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/core': 3.693.0 - '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-node': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) '@aws-sdk/middleware-host-header': 3.693.0 '@aws-sdk/middleware-logger': 3.693.0 '@aws-sdk/middleware-recursion-detection': 3.693.0 @@ -3932,8 +472,11 @@ snapshots: tslib: 2.8.1 transitivePeerDependencies: - aws-crt + dev: false - '@aws-sdk/core@3.693.0': + /@aws-sdk/core@3.693.0: + resolution: {integrity: sha512-v6Z/kWmLFqRLDPEwl9hJGhtTgIFHjZugSfF1Yqffdxf4n1AWgtHS7qSegakuMyN5pP4K2tvUD8qHJ+gGe2Bw2A==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/core': 2.5.3 @@ -3946,16 +489,22 @@ snapshots: '@smithy/util-middleware': 3.0.10 fast-xml-parser: 4.4.1 tslib: 2.8.1 + dev: false - '@aws-sdk/credential-provider-env@3.693.0': + /@aws-sdk/credential-provider-env@3.693.0: + resolution: {integrity: sha512-hMUZaRSF7+iBKZfBHNLihFs9zvpM1CB8MBOTnTp5NGCVkRYF3SB2LH+Kcippe0ats4qCyB1eEoyQX99rERp2iQ==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/core': 3.693.0 '@aws-sdk/types': 3.692.0 '@smithy/property-provider': 3.1.10 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/credential-provider-http@3.693.0': + /@aws-sdk/credential-provider-http@3.693.0: + resolution: {integrity: sha512-sL8MvwNJU7ZpD7/d2VVb3by1GknIJUxzTIgYtVkDVA/ojo+KRQSSHxcj0EWWXF5DTSh2Tm+LrEug3y1ZyKHsDA==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/core': 3.693.0 '@aws-sdk/types': 3.692.0 @@ -3967,15 +516,20 @@ snapshots: '@smithy/types': 3.7.1 '@smithy/util-stream': 3.3.1 tslib: 2.8.1 + dev: false - '@aws-sdk/credential-provider-ini@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0)': + /@aws-sdk/credential-provider-ini@3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0): + resolution: {integrity: sha512-kvaa4mXhCCOuW7UQnBhYqYfgWmwy7WSBSDClutwSLPZvgrhYj2l16SD2lN4IfYdxARYMJJ1lFYp3/jJG/9Yk4Q==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.693.0 dependencies: '@aws-sdk/client-sts': 3.693.0 '@aws-sdk/core': 3.693.0 '@aws-sdk/credential-provider-env': 3.693.0 '@aws-sdk/credential-provider-http': 3.693.0 '@aws-sdk/credential-provider-process': 3.693.0 - '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)) + '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0) '@aws-sdk/credential-provider-web-identity': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/types': 3.692.0 '@smithy/credential-provider-imds': 3.2.7 @@ -3986,14 +540,17 @@ snapshots: transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt + dev: false - '@aws-sdk/credential-provider-node@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0)': + /@aws-sdk/credential-provider-node@3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0): + resolution: {integrity: sha512-42WMsBjTNnjYxYuM3qD/Nq+8b7UdMopUq5OduMDxoM3mFTV6PXMMnfI4Z1TNnR4tYRvPXAnuNltF6xmjKbSJRA==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/credential-provider-env': 3.693.0 '@aws-sdk/credential-provider-http': 3.693.0 - '@aws-sdk/credential-provider-ini': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))(@aws-sdk/client-sts@3.693.0) + '@aws-sdk/credential-provider-ini': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0)(@aws-sdk/client-sts@3.693.0) '@aws-sdk/credential-provider-process': 3.693.0 - '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)) + '@aws-sdk/credential-provider-sso': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0) '@aws-sdk/credential-provider-web-identity': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/types': 3.692.0 '@smithy/credential-provider-imds': 3.2.7 @@ -4005,8 +562,11 @@ snapshots: - '@aws-sdk/client-sso-oidc' - '@aws-sdk/client-sts' - aws-crt + dev: false - '@aws-sdk/credential-provider-process@3.693.0': + /@aws-sdk/credential-provider-process@3.693.0: + resolution: {integrity: sha512-cvxQkrTWHHjeHrPlj7EWXPnFSq8x7vMx+Zn1oTsMpCY445N9KuzjfJTkmNGwU2GT6rSZI9/0MM02aQvl5bBBTQ==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/core': 3.693.0 '@aws-sdk/types': 3.692.0 @@ -4014,12 +574,15 @@ snapshots: '@smithy/shared-ini-file-loader': 3.1.11 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/credential-provider-sso@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))': + /@aws-sdk/credential-provider-sso@3.693.0(@aws-sdk/client-sso-oidc@3.693.0): + resolution: {integrity: sha512-479UlJxY+BFjj3pJFYUNC0DCMrykuG7wBAXfsvZqQxKUa83DnH5Q1ID/N2hZLkxjGd4ZW0AC3lTOMxFelGzzpQ==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/client-sso': 3.693.0 '@aws-sdk/core': 3.693.0 - '@aws-sdk/token-providers': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0)) + '@aws-sdk/token-providers': 3.693.0(@aws-sdk/client-sso-oidc@3.693.0) '@aws-sdk/types': 3.692.0 '@smithy/property-provider': 3.1.10 '@smithy/shared-ini-file-loader': 3.1.11 @@ -4028,8 +591,13 @@ snapshots: transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt + dev: false - '@aws-sdk/credential-provider-web-identity@3.693.0(@aws-sdk/client-sts@3.693.0)': + /@aws-sdk/credential-provider-web-identity@3.693.0(@aws-sdk/client-sts@3.693.0): + resolution: {integrity: sha512-8LB210Pr6VeCiSb2hIra+sAH4KUBLyGaN50axHtIgufVK8jbKIctTZcVY5TO9Se+1107TsruzeXS7VeqVdJfFA==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sts': ^3.693.0 dependencies: '@aws-sdk/client-sts': 3.693.0 '@aws-sdk/core': 3.693.0 @@ -4037,8 +605,11 @@ snapshots: '@smithy/property-provider': 3.1.10 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-bucket-endpoint@3.693.0': + /@aws-sdk/middleware-bucket-endpoint@3.693.0: + resolution: {integrity: sha512-cPIa+lxMYiFRHtxKfNIVSFGO6LSgZCk42pu3d7KGwD6hu6vXRD5B2/DD3rPcEH1zgl2j0Kx1oGAV7SRXKHSFag==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@aws-sdk/util-arn-parser': 3.693.0 @@ -4047,15 +618,21 @@ snapshots: '@smithy/types': 3.7.1 '@smithy/util-config-provider': 3.0.0 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-expect-continue@3.693.0': + /@aws-sdk/middleware-expect-continue@3.693.0: + resolution: {integrity: sha512-MuK/gsJWpHz6Tv0CqTCS+QNOxLa2RfPh1biVCu/uO3l7kA0TjQ/C+tfgKvLXeH103tuDrOVINK+bt2ENmI3SWg==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/protocol-http': 4.1.7 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-flexible-checksums@3.693.0': + /@aws-sdk/middleware-flexible-checksums@3.693.0: + resolution: {integrity: sha512-xkS6zjuE11ob93H9t65kHzphXcUMnN2SmIm2wycUPg+hi8Q6DJA6U2p//6oXkrr9oHy1QvwtllRd7SAd63sFKQ==} + engines: {node: '>=16.0.0'} dependencies: '@aws-crypto/crc32': 5.2.0 '@aws-crypto/crc32c': 5.2.0 @@ -4070,34 +647,49 @@ snapshots: '@smithy/util-stream': 3.3.1 '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-host-header@3.693.0': + /@aws-sdk/middleware-host-header@3.693.0: + resolution: {integrity: sha512-BCki6sAZ5jYwIN/t3ElCiwerHad69ipHwPsDCxJQyeiOnJ8HG+lEpnVIfrnI8A0fLQNSF3Gtx6ahfBpKiv1Oug==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/protocol-http': 4.1.7 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-location-constraint@3.693.0': + /@aws-sdk/middleware-location-constraint@3.693.0: + resolution: {integrity: sha512-eDAExTZ9uNIP7vs2JCVCOuWJauGueisBSn+Ovt7UvvuEUp6KOIJqn8oFxWmyUQu2GvbG4OcaTLgbqD95YHTB0Q==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-logger@3.693.0': + /@aws-sdk/middleware-logger@3.693.0: + resolution: {integrity: sha512-dXnXDPr+wIiJ1TLADACI1g9pkSB21KkMIko2u4CJ2JCBoxi5IqeTnVoa6YcC8GdFNVRl+PorZ3Zqfmf1EOTC6w==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-recursion-detection@3.693.0': + /@aws-sdk/middleware-recursion-detection@3.693.0: + resolution: {integrity: sha512-0LDmM+VxXp0u3rG0xQRWD/q6Ubi7G8I44tBPahevD5CaiDZTkmNTrVUf0VEJgVe0iCKBppACMBDkLB0/ETqkFw==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/protocol-http': 4.1.7 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-sdk-s3@3.693.0': + /@aws-sdk/middleware-sdk-s3@3.693.0: + resolution: {integrity: sha512-5A++RBjJ3guyq5pbYs+Oq5hMlA8CK2OWaHx09cxVfhHWl/RoaY8DXrft4gnhoUEBrrubyMw7r9j7RIMLvS58kg==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/core': 3.693.0 '@aws-sdk/types': 3.692.0 @@ -4113,14 +705,20 @@ snapshots: '@smithy/util-stream': 3.3.1 '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-ssec@3.693.0': + /@aws-sdk/middleware-ssec@3.693.0: + resolution: {integrity: sha512-Ro5vzI7SRgEeuoMk3fKqFjGv6mG4c7VsSCDwnkiasmafQFBTPvUIpgmu2FXMHqW/OthvoiOzpSrlJ9Bwlx2f8A==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/middleware-user-agent@3.693.0': + /@aws-sdk/middleware-user-agent@3.693.0: + resolution: {integrity: sha512-/KUq/KEpFFbQmNmpp7SpAtFAdViquDfD2W0QcG07zYBfz9MwE2ig48ALynXm5sMpRmnG7sJXjdvPtTsSVPfkiw==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/core': 3.693.0 '@aws-sdk/types': 3.692.0 @@ -4129,8 +727,11 @@ snapshots: '@smithy/protocol-http': 4.1.7 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/region-config-resolver@3.693.0': + /@aws-sdk/region-config-resolver@3.693.0: + resolution: {integrity: sha512-YLUkMsUY0GLW/nfwlZ69cy1u07EZRmsv8Z9m0qW317/EZaVx59hcvmcvb+W4bFqj5E8YImTjoGfE4cZ0F9mkyw==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/node-config-provider': 3.1.11 @@ -4138,8 +739,11 @@ snapshots: '@smithy/util-config-provider': 3.0.0 '@smithy/util-middleware': 3.0.10 tslib: 2.8.1 + dev: false - '@aws-sdk/signature-v4-multi-region@3.693.0': + /@aws-sdk/signature-v4-multi-region@3.693.0: + resolution: {integrity: sha512-s7zbbsoVIriTR4ZGaateKuTqz6ddpazAyHvjk7I9kd+NvGNPiuAI18UdbuiiRI6K5HuYKf1ah6mKWFGPG15/kQ==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/middleware-sdk-s3': 3.693.0 '@aws-sdk/types': 3.692.0 @@ -4147,8 +751,13 @@ snapshots: '@smithy/signature-v4': 4.2.3 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/token-providers@3.693.0(@aws-sdk/client-sso-oidc@3.693.0(@aws-sdk/client-sts@3.693.0))': + /@aws-sdk/token-providers@3.693.0(@aws-sdk/client-sso-oidc@3.693.0): + resolution: {integrity: sha512-nDBTJMk1l/YmFULGfRbToOA2wjf+FkQT4dMgYCv+V9uSYsMzQj8A7Tha2dz9yv4vnQgYaEiErQ8d7HVyXcVEoA==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@aws-sdk/client-sso-oidc': ^3.693.0 dependencies: '@aws-sdk/client-sso-oidc': 3.693.0(@aws-sdk/client-sts@3.693.0) '@aws-sdk/types': 3.692.0 @@ -4156,56 +765,90 @@ snapshots: '@smithy/shared-ini-file-loader': 3.1.11 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/types@3.692.0': + /@aws-sdk/types@3.692.0: + resolution: {integrity: sha512-RpNvzD7zMEhiKgmlxGzyXaEcg2khvM7wd5sSHVapOcrde1awQSOMGI4zKBQ+wy5TnDfrm170ROz/ERLYtrjPZA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/util-arn-parser@3.693.0': + /@aws-sdk/util-arn-parser@3.693.0: + resolution: {integrity: sha512-WC8x6ca+NRrtpAH64rWu+ryDZI3HuLwlEr8EU6/dbC/pt+r/zC0PBoC15VEygUaBA+isppCikQpGyEDu0Yj7gQ==} + engines: {node: '>=16.0.0'} dependencies: tslib: 2.8.1 + dev: false - '@aws-sdk/util-endpoints@3.693.0': + /@aws-sdk/util-endpoints@3.693.0: + resolution: {integrity: sha512-eo4F6DRQ/kxS3gxJpLRv+aDNy76DxQJL5B3DPzpr9Vkq0ygVoi4GT5oIZLVaAVIJmi6k5qq9dLsYZfWLUxJJSg==} + engines: {node: '>=16.0.0'} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/types': 3.7.1 '@smithy/util-endpoints': 2.1.6 tslib: 2.8.1 + dev: false - '@aws-sdk/util-locate-window@3.693.0': + /@aws-sdk/util-locate-window@3.693.0: + resolution: {integrity: sha512-ttrag6haJLWABhLqtg1Uf+4LgHWIMOVSYL+VYZmAp2v4PUGOwWmWQH0Zk8RM7YuQcLfH/EoR72/Yxz6A4FKcuw==} + engines: {node: '>=16.0.0'} dependencies: tslib: 2.8.1 + dev: false - '@aws-sdk/util-user-agent-browser@3.693.0': + /@aws-sdk/util-user-agent-browser@3.693.0: + resolution: {integrity: sha512-6EUfuKOujtddy18OLJUaXfKBgs+UcbZ6N/3QV4iOkubCUdeM1maIqs++B9bhCbWeaeF5ORizJw5FTwnyNjE/mw==} dependencies: '@aws-sdk/types': 3.692.0 '@smithy/types': 3.7.1 bowser: 2.11.0 tslib: 2.8.1 + dev: false - '@aws-sdk/util-user-agent-node@3.693.0': + /@aws-sdk/util-user-agent-node@3.693.0: + resolution: {integrity: sha512-td0OVX8m5ZKiXtecIDuzY3Y3UZIzvxEr57Hp21NOwieqKCG2UeyQWWeGPv0FQaU7dpTkvFmVNI+tx9iB8V/Nhg==} + engines: {node: '>=16.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true dependencies: '@aws-sdk/middleware-user-agent': 3.693.0 '@aws-sdk/types': 3.692.0 '@smithy/node-config-provider': 3.1.11 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@aws-sdk/xml-builder@3.693.0': + /@aws-sdk/xml-builder@3.693.0: + resolution: {integrity: sha512-C/rPwJcqnV8VDr2/VtcQnymSpcfEEgH1Jm6V0VmfXNZFv4Qzf1eCS8nsec0gipYgZB+cBBjfXw5dAk6pJ8ubpw==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@babel/code-frame@7.26.2': + /@babel/code-frame@7.26.2: + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} + engines: {node: '>=6.9.0'} dependencies: '@babel/helper-validator-identifier': 7.25.9 js-tokens: 4.0.0 picocolors: 1.1.1 + dev: true - '@babel/compat-data@7.26.2': {} + /@babel/compat-data@7.26.2: + resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} + engines: {node: '>=6.9.0'} + dev: true - '@babel/core@7.26.0': + /@babel/core@7.26.0: + resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} + engines: {node: '>=6.9.0'} dependencies: '@ampproject/remapping': 2.3.0 '@babel/code-frame': 7.26.2 @@ -4224,31 +867,45 @@ snapshots: semver: 6.3.1 transitivePeerDependencies: - supports-color + dev: true - '@babel/generator@7.26.2': + /@babel/generator@7.26.2: + resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} + engines: {node: '>=6.9.0'} dependencies: '@babel/parser': 7.26.2 '@babel/types': 7.26.0 '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.0.2 + dev: true - '@babel/helper-compilation-targets@7.25.9': + /@babel/helper-compilation-targets@7.25.9: + resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} + engines: {node: '>=6.9.0'} dependencies: '@babel/compat-data': 7.26.2 '@babel/helper-validator-option': 7.25.9 browserslist: 4.24.2 lru-cache: 5.1.1 semver: 6.3.1 + dev: true - '@babel/helper-module-imports@7.25.9': + /@babel/helper-module-imports@7.25.9: + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} + engines: {node: '>=6.9.0'} dependencies: '@babel/traverse': 7.25.9 '@babel/types': 7.26.0 transitivePeerDependencies: - supports-color + dev: true - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': + /@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0): + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.26.0 '@babel/helper-module-imports': 7.25.9 @@ -4256,116 +913,215 @@ snapshots: '@babel/traverse': 7.25.9 transitivePeerDependencies: - supports-color + dev: true - '@babel/helper-plugin-utils@7.25.9': {} + /@babel/helper-plugin-utils@7.25.9: + resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} + engines: {node: '>=6.9.0'} + dev: true - '@babel/helper-string-parser@7.25.9': {} + /@babel/helper-string-parser@7.25.9: + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} + engines: {node: '>=6.9.0'} + dev: true - '@babel/helper-validator-identifier@7.25.9': {} + /@babel/helper-validator-identifier@7.25.9: + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} + engines: {node: '>=6.9.0'} + dev: true - '@babel/helper-validator-option@7.25.9': {} + /@babel/helper-validator-option@7.25.9: + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} + engines: {node: '>=6.9.0'} + dev: true - '@babel/helpers@7.26.0': + /@babel/helpers@7.26.0: + resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} + engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.25.9 '@babel/types': 7.26.0 + dev: true - '@babel/parser@7.26.2': + /@babel/parser@7.26.2: + resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} + engines: {node: '>=6.0.0'} + hasBin: true dependencies: '@babel/types': 7.26.0 + dev: true - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.0)': + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.26.0): + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.0)': + /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.26.0): + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.0)': + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.26.0): + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.0)': + /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.26.0): + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.0)': + /@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.0): + resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.0)': + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.26.0): + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.0)': + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.26.0): + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': + /@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0): + resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.0)': + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.26.0): + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.0)': + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.26.0): + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.0)': + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.26.0): + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.0)': + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.26.0): + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.0)': + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.26.0): + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.0)': + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.26.0): + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.0)': + /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.26.0): + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.0)': + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.26.0): + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)': + /@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0): + resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 dependencies: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + dev: true - '@babel/template@7.25.9': + /@babel/template@7.25.9: + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} + engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.26.2 '@babel/parser': 7.26.2 '@babel/types': 7.26.0 + dev: true - '@babel/traverse@7.25.9': + /@babel/traverse@7.25.9: + resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} + engines: {node: '>=6.9.0'} dependencies: '@babel/code-frame': 7.26.2 '@babel/generator': 7.26.2 @@ -4376,29 +1132,51 @@ snapshots: globals: 11.12.0 transitivePeerDependencies: - supports-color + dev: true - '@babel/types@7.26.0': + /@babel/types@7.26.0: + resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} + engines: {node: '>=6.9.0'} dependencies: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 + dev: true - '@bcoe/v8-coverage@0.2.3': {} + /@bcoe/v8-coverage@0.2.3: + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + dev: true - '@colors/colors@1.5.0': + /@colors/colors@1.5.0: + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + requiresBuild: true + dev: true optional: true - '@cspotcode/source-map-support@0.8.1': + /@cspotcode/source-map-support@0.8.1: + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@eslint-community/eslint-utils@4.4.1(eslint@8.0.0)': + /@eslint-community/eslint-utils@4.4.1(eslint@8.0.0): + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: eslint: 8.0.0 eslint-visitor-keys: 3.4.3 + dev: true - '@eslint-community/regexpp@4.12.1': {} + /@eslint-community/regexpp@4.12.1: + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: true - '@eslint/eslintrc@1.4.1': + /@eslint/eslintrc@1.4.1: + resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 debug: 4.3.7 @@ -4411,45 +1189,71 @@ snapshots: strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color + dev: true - '@faker-js/faker@8.4.1': {} + /@faker-js/faker@8.4.1: + resolution: {integrity: sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0, npm: '>=6.14.13'} + dev: false - '@hapi/hoek@9.3.0': {} + /@hapi/hoek@9.3.0: + resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + dev: false - '@hapi/topo@5.1.0': + /@hapi/topo@5.1.0: + resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} dependencies: '@hapi/hoek': 9.3.0 + dev: false - '@humanwhocodes/config-array@0.6.0': + /@humanwhocodes/config-array@0.6.0: + resolution: {integrity: sha512-JQlEKbcgEUjBFhLIF4iqM7u/9lwgHRBcpHrmUNCALK0Q3amXN6lxdoXLnF0sm11E9VqTmBALR87IlUg1bZ8A9A==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead dependencies: '@humanwhocodes/object-schema': 1.2.1 debug: 4.3.7 minimatch: 3.1.2 transitivePeerDependencies: - supports-color + dev: true - '@humanwhocodes/object-schema@1.2.1': {} + /@humanwhocodes/object-schema@1.2.1: + resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + deprecated: Use @eslint/object-schema instead + dev: true - '@isaacs/cliui@8.0.2': + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} dependencies: string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 + string-width-cjs: /string-width@4.2.3 strip-ansi: 7.1.0 - strip-ansi-cjs: strip-ansi@6.0.1 + strip-ansi-cjs: /strip-ansi@6.0.1 wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: false - '@istanbuljs/load-nyc-config@1.1.0': + /@istanbuljs/load-nyc-config@1.1.0: + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} dependencies: camelcase: 5.3.1 find-up: 4.1.0 get-package-type: 0.1.0 js-yaml: 3.14.1 resolve-from: 5.0.0 + dev: true - '@istanbuljs/schema@0.1.3': {} + /@istanbuljs/schema@0.1.3: + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + dev: true - '@jest/console@29.7.0': + /@jest/console@29.7.0: + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 '@types/node': 20.3.1 @@ -4457,8 +1261,16 @@ snapshots: jest-message-util: 29.7.0 jest-util: 29.7.0 slash: 3.0.0 + dev: true - '@jest/core@29.7.0(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3))': + /@jest/core@29.7.0(ts-node@10.9.1): + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0 @@ -4472,7 +1284,7 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) + jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -4492,26 +1304,38 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + dev: true - '@jest/environment@29.7.0': + /@jest/environment@29.7.0: + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 '@types/node': 20.3.1 jest-mock: 29.7.0 + dev: true - '@jest/expect-utils@29.7.0': + /@jest/expect-utils@29.7.0: + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: jest-get-type: 29.6.3 + dev: true - '@jest/expect@29.7.0': + /@jest/expect@29.7.0: + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: expect: 29.7.0 jest-snapshot: 29.7.0 transitivePeerDependencies: - supports-color + dev: true - '@jest/fake-timers@29.7.0': + /@jest/fake-timers@29.7.0: + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 @@ -4519,8 +1343,11 @@ snapshots: jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 + dev: true - '@jest/globals@29.7.0': + /@jest/globals@29.7.0: + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/environment': 29.7.0 '@jest/expect': 29.7.0 @@ -4528,8 +1355,16 @@ snapshots: jest-mock: 29.7.0 transitivePeerDependencies: - supports-color + dev: true - '@jest/reporters@29.7.0': + /@jest/reporters@29.7.0: + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true dependencies: '@bcoe/v8-coverage': 0.2.3 '@jest/console': 29.7.0 @@ -4557,32 +1392,47 @@ snapshots: v8-to-istanbul: 9.3.0 transitivePeerDependencies: - supports-color + dev: true - '@jest/schemas@29.6.3': + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@sinclair/typebox': 0.27.8 + dev: true - '@jest/source-map@29.6.3': + /@jest/source-map@29.6.3: + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jridgewell/trace-mapping': 0.3.25 callsites: 3.1.0 graceful-fs: 4.2.11 + dev: true - '@jest/test-result@29.7.0': + /@jest/test-result@29.7.0: + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/console': 29.7.0 '@jest/types': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 collect-v8-coverage: 1.0.2 + dev: true - '@jest/test-sequencer@29.7.0': + /@jest/test-sequencer@29.7.0: + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/test-result': 29.7.0 graceful-fs: 4.2.11 jest-haste-map: 29.7.0 slash: 3.0.0 + dev: true - '@jest/transform@29.7.0': + /@jest/transform@29.7.0: + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/core': 7.26.0 '@jest/types': 29.6.3 @@ -4601,8 +1451,11 @@ snapshots: write-file-atomic: 4.0.2 transitivePeerDependencies: - supports-color + dev: true - '@jest/types@29.6.3': + /@jest/types@29.6.3: + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 @@ -4610,51 +1463,89 @@ snapshots: '@types/node': 20.3.1 '@types/yargs': 17.0.33 chalk: 4.1.2 + dev: true - '@jridgewell/gen-mapping@0.3.5': + /@jridgewell/gen-mapping@0.3.5: + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} dependencies: '@jridgewell/set-array': 1.2.1 '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.25 + dev: true - '@jridgewell/resolve-uri@3.1.2': {} + /@jridgewell/resolve-uri@3.1.2: + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} - '@jridgewell/set-array@1.2.1': {} + /@jridgewell/set-array@1.2.1: + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + dev: true - '@jridgewell/source-map@0.3.6': + /@jridgewell/source-map@0.3.6: + resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} dependencies: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 + dev: true - '@jridgewell/sourcemap-codec@1.5.0': {} + /@jridgewell/sourcemap-codec@1.5.0: + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - '@jridgewell/trace-mapping@0.3.25': + /@jridgewell/trace-mapping@0.3.25: + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + dev: true - '@jridgewell/trace-mapping@0.3.9': + /@jridgewell/trace-mapping@0.3.9: + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@lukeed/csprng@1.1.0': {} + /@lukeed/csprng@1.1.0: + resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} + engines: {node: '>=8'} - '@microsoft/tsdoc@0.15.0': {} + /@microsoft/tsdoc@0.15.0: + resolution: {integrity: sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA==} + dev: false - '@nestjs/axios@3.1.2(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(axios@1.7.7)(rxjs@7.8.1)': + /@nestjs/axios@3.1.2(@nestjs/common@10.0.0)(axios@1.7.7)(rxjs@7.8.1): + resolution: {integrity: sha512-pFlfi4ZQsZtTNNhvgssbxjCHUd1nMpV3sXy/xOOB2uEJhw3M8j8SFR08gjFNil2we2Har7VCsXLfCkwbMHECFQ==} + peerDependencies: + '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + axios: ^1.3.1 + rxjs: ^6.0.0 || ^7.0.0 dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) axios: 1.7.7 rxjs: 7.8.1 + dev: false - '@nestjs/class-validator@0.13.1': + /@nestjs/class-validator@0.13.1: + resolution: {integrity: sha512-Wwpg9KuGnkWOFleBPEFdHQKwqZsF/PFWQaeSaXwatkofgqD7N6AV5J30xIVgScDP+IcE+MdPGZJ38vHQ24KxPQ==} dependencies: '@types/validator': 13.12.2 libphonenumber-js: 1.11.14 validator: 13.12.0 + dev: false - '@nestjs/cli@10.0.0': + /@nestjs/cli@10.0.0: + resolution: {integrity: sha512-14pju3ejAAUpFe1iK99v/b7Bw96phBMV58GXTSm3TcdgaI4O7UTLXTbMiUNyU+LGr/1CPIfThcWqFyKhDIC9VQ==} + engines: {node: '>= 16'} + hasBin: true + peerDependencies: + '@swc/cli': ^0.1.62 + '@swc/core': ^1.3.62 + peerDependenciesMeta: + '@swc/cli': + optional: true + '@swc/core': + optional: true dependencies: '@angular-devkit/core': 16.1.0(chokidar@3.5.3) '@angular-devkit/schematics': 16.1.0(chokidar@3.5.3) @@ -4682,29 +1573,62 @@ snapshots: - esbuild - uglify-js - webpack-cli + dev: true - '@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1)': + /@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1): + resolution: {integrity: sha512-Fa2GDQJrO5TTTcpISWfm0pdPS62V+8YbxeG5CA01zMUI+dCO3v3oFf+BSjqCGUUo7GDNzDsjAejwGXuqA54RPw==} + peerDependencies: + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 + rxjs: ^7.1.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true dependencies: + class-transformer: 0.5.1 + class-validator: 0.14.1 iterare: 1.2.1 reflect-metadata: 0.2.0 rxjs: 7.8.1 tslib: 2.5.3 uid: 2.0.2 - optionalDependencies: - class-transformer: 0.5.1 - class-validator: 0.14.1 - '@nestjs/config@3.3.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(rxjs@7.8.1)': + /@nestjs/config@3.3.0(@nestjs/common@10.0.0)(rxjs@7.8.1): + resolution: {integrity: sha512-pdGTp8m9d0ZCrjTpjkUbZx6gyf2IKf+7zlkrPNMsJzYZ4bFRRTpXrnj+556/5uiI6AfL5mMrJc2u7dB6bvM+VA==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + rxjs: ^7.1.0 dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) dotenv: 16.4.5 dotenv-expand: 10.0.0 lodash: 4.17.21 rxjs: 7.8.1 + dev: false - '@nestjs/core@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)': + /@nestjs/core@10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1): + resolution: {integrity: sha512-HFTdj4vsF+2qOaq97ZPRDle6Q/KyL5lmMah0/ZR0ie+e1/tnlvmlqw589xFACTemLJFFOjZMy763v+icO9u72w==} + requiresBuild: true + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/microservices': ^10.0.0 + '@nestjs/platform-express': ^10.0.0 + '@nestjs/websockets': ^10.0.0 + reflect-metadata: ^0.1.12 + rxjs: ^7.1.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true + '@nestjs/websockets': + optional: true dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0) '@nuxtjs/opencollective': 0.3.2 fast-safe-stringify: 2.1.1 iterare: 1.2.1 @@ -4713,29 +1637,46 @@ snapshots: rxjs: 7.8.1 tslib: 2.5.3 uid: 2.0.2 - optionalDependencies: - '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) transitivePeerDependencies: - encoding - '@nestjs/jwt@10.2.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))': + /@nestjs/jwt@10.2.0(@nestjs/common@10.0.0): + resolution: {integrity: sha512-x8cG90SURkEiLOehNaN2aRlotxT0KZESUliOPKKnjWiyJOcWurkF3w345WOX0P4MgFzUjGoZ1Sy0aZnxeihT0g==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) '@types/jsonwebtoken': 9.0.5 jsonwebtoken: 9.0.2 + dev: false - '@nestjs/mapped-types@2.0.6(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)': + /@nestjs/mapped-types@2.0.6(@nestjs/common@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0): + resolution: {integrity: sha512-84ze+CPfp1OWdpRi1/lOu59hOhTz38eVzJvRKrg9ykRFwDz+XleKfMsG0gUqNZYFa6v53XYzeD+xItt8uDW7NQ==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + class-transformer: ^0.4.0 || ^0.5.0 + class-validator: ^0.13.0 || ^0.14.0 + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + class-transformer: + optional: true + class-validator: + optional: true dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - reflect-metadata: 0.2.0 - optionalDependencies: class-transformer: 0.5.1 class-validator: 0.14.1 + reflect-metadata: 0.2.0 + dev: false - '@nestjs/platform-express@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)': + /@nestjs/platform-express@10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0): + resolution: {integrity: sha512-jOQBPVpk7B4JFXZZwxHSsY6odIqZlea9CbqKzu/hfDyqRv+AwuJk5gprvvL6RpWAHNyRMH1r5/14bqcXD3+WGw==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/core': ^10.0.0 dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) body-parser: 1.20.2 cors: 2.8.5 express: 4.18.2 @@ -4744,7 +1685,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@nestjs/schematics@10.0.0(chokidar@3.5.3)(typescript@5.1.3)': + /@nestjs/schematics@10.0.0(chokidar@3.5.3)(typescript@5.1.3): + resolution: {integrity: sha512-gfUy/N1m1paN33BXq4d7HoCM+zM4rFxYjqAb8jkrBfBHiwyEhHHozfX/aRy/kOnAcy/VP8v4Zs4HKKrbRRlHnw==} + peerDependencies: + typescript: '>=4.8.2' dependencies: '@angular-devkit/core': 16.1.0(chokidar@3.5.3) '@angular-devkit/schematics': 16.1.0(chokidar@3.5.3) @@ -4754,52 +1698,96 @@ snapshots: typescript: 5.1.3 transitivePeerDependencies: - chokidar + dev: true - '@nestjs/swagger@8.0.5(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)': + /@nestjs/swagger@8.0.5(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0): + resolution: {integrity: sha512-ZmBdsbQNs3wIN5kCuvAVbz3/ULh3gi814oHTP49uTqAGi1aT0YSatUyncwQOHBOlRT+rwF+TNjoAsZ+twIk/Jw==} + peerDependencies: + '@fastify/static': ^6.0.0 || ^7.0.0 + '@nestjs/common': ^9.0.0 || ^10.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 + class-transformer: '*' + class-validator: '*' + reflect-metadata: ^0.1.12 || ^0.2.0 + peerDependenciesMeta: + '@fastify/static': + optional: true + class-transformer: + optional: true + class-validator: + optional: true dependencies: '@microsoft/tsdoc': 0.15.0 '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/mapped-types': 2.0.6(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/mapped-types': 2.0.6(@nestjs/common@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0) + class-transformer: 0.5.1 + class-validator: 0.14.1 js-yaml: 4.1.0 lodash: 4.17.21 path-to-regexp: 3.3.0 reflect-metadata: 0.2.0 swagger-ui-dist: 5.18.2 - optionalDependencies: - class-transformer: 0.5.1 - class-validator: 0.14.1 + dev: false - '@nestjs/testing@10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0)': + /@nestjs/testing@10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(@nestjs/platform-express@10.0.0): + resolution: {integrity: sha512-U5q3+svkddpdSk51ZFCEnFpQuWxAwE4ahsX77FrqqCAYidr7HUtL/BHYOVzI5H9vUH6BvJxMbfo3tiUXQl/2aA==} + peerDependencies: + '@nestjs/common': ^10.0.0 + '@nestjs/core': ^10.0.0 + '@nestjs/microservices': ^10.0.0 + '@nestjs/platform-express': ^10.0.0 + peerDependenciesMeta: + '@nestjs/microservices': + optional: true + '@nestjs/platform-express': + optional: true dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0) tslib: 2.5.3 - optionalDependencies: - '@nestjs/platform-express': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0) + dev: true - '@nestjs/typeorm@10.0.2(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)))': + /@nestjs/typeorm@10.0.2(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1)(typeorm@0.3.20): + resolution: {integrity: sha512-H738bJyydK4SQkRCTeh1aFBxoO1E9xdL/HaLGThwrqN95os5mEyAtK7BLADOS+vldP4jDZ2VQPLj4epWwRqCeQ==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + reflect-metadata: ^0.1.13 || ^0.2.0 + rxjs: ^7.2.0 + typeorm: ^0.3.0 dependencies: '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1) - '@nestjs/core': 10.0.0(@nestjs/common@10.0.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.0)(rxjs@7.8.1))(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.2.0)(rxjs@7.8.1) reflect-metadata: 0.2.0 rxjs: 7.8.1 - typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) + typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1) uuid: 9.0.1 + dev: false - '@nodelib/fs.scandir@2.1.5': + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 - '@nodelib/fs.stat@2.0.5': {} + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} - '@nodelib/fs.walk@1.2.8': + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} dependencies: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 - '@nuxtjs/opencollective@0.3.2': + /@nuxtjs/opencollective@0.3.2: + resolution: {integrity: sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true dependencies: chalk: 4.1.2 consola: 2.15.3 @@ -4807,56 +1795,93 @@ snapshots: transitivePeerDependencies: - encoding - '@phc/format@1.0.0': {} + /@phc/format@1.0.0: + resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==} + engines: {node: '>=10'} + dev: false - '@pkgjs/parseargs@0.11.0': + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + dev: false optional: true - '@pkgr/core@0.1.1': {} + /@pkgr/core@0.1.1: + resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + dev: true - '@scarf/scarf@1.4.0': {} + /@scarf/scarf@1.4.0: + resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} + requiresBuild: true + dev: false - '@sideway/address@4.1.5': + /@sideway/address@4.1.5: + resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} dependencies: '@hapi/hoek': 9.3.0 + dev: false - '@sideway/formula@3.0.1': {} + /@sideway/formula@3.0.1: + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + dev: false - '@sideway/pinpoint@2.0.0': {} + /@sideway/pinpoint@2.0.0: + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + dev: false - '@sinclair/typebox@0.27.8': {} + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true - '@sinonjs/commons@3.0.1': + /@sinonjs/commons@3.0.1: + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} dependencies: type-detect: 4.0.8 + dev: true - '@sinonjs/fake-timers@10.3.0': + /@sinonjs/fake-timers@10.3.0: + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} dependencies: '@sinonjs/commons': 3.0.1 + dev: true - '@smithy/abort-controller@3.1.8': + /@smithy/abort-controller@3.1.8: + resolution: {integrity: sha512-+3DOBcUn5/rVjlxGvUPKc416SExarAQ+Qe0bqk30YSUjbepwpS7QN0cyKUSifvLJhdMZ0WPzPP5ymut0oonrpQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/chunked-blob-reader-native@3.0.1': + /@smithy/chunked-blob-reader-native@3.0.1: + resolution: {integrity: sha512-VEYtPvh5rs/xlyqpm5NRnfYLZn+q0SRPELbvBV+C/G7IQ+ouTuo+NKKa3ShG5OaFR8NYVMXls9hPYLTvIKKDrQ==} dependencies: '@smithy/util-base64': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/chunked-blob-reader@4.0.0': + /@smithy/chunked-blob-reader@4.0.0: + resolution: {integrity: sha512-jSqRnZvkT4egkq/7b6/QRCNXmmYVcHwnJldqJ3IhVpQE2atObVJ137xmGeuGFhjFUr8gCEVAOKwSY79OvpbDaQ==} dependencies: tslib: 2.8.1 + dev: false - '@smithy/config-resolver@3.0.12': + /@smithy/config-resolver@3.0.12: + resolution: {integrity: sha512-YAJP9UJFZRZ8N+UruTeq78zkdjUHmzsY62J4qKWZ4SXB4QXJ/+680EfXXgkYA2xj77ooMqtUY9m406zGNqwivQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/node-config-provider': 3.1.11 '@smithy/types': 3.7.1 '@smithy/util-config-provider': 3.0.0 '@smithy/util-middleware': 3.0.10 tslib: 2.8.1 + dev: false - '@smithy/core@2.5.3': + /@smithy/core@2.5.3: + resolution: {integrity: sha512-96uW8maifUSmehaeW7uydWn7wBc98NEeNI3zN8vqakGpyCQgzyJaA64Z4FCOUmAdCJkhppd/7SZ798Fo4Xx37g==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/middleware-serde': 3.0.10 '@smithy/protocol-http': 4.1.7 @@ -4866,99 +1891,142 @@ snapshots: '@smithy/util-stream': 3.3.1 '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/credential-provider-imds@3.2.7': + /@smithy/credential-provider-imds@3.2.7: + resolution: {integrity: sha512-cEfbau+rrWF8ylkmmVAObOmjbTIzKyUC5TkBL58SbLywD0RCBC4JAUKbmtSm2w5KUJNRPGgpGFMvE2FKnuNlWQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/node-config-provider': 3.1.11 '@smithy/property-provider': 3.1.10 '@smithy/types': 3.7.1 '@smithy/url-parser': 3.0.10 tslib: 2.8.1 + dev: false - '@smithy/eventstream-codec@3.1.9': + /@smithy/eventstream-codec@3.1.9: + resolution: {integrity: sha512-F574nX0hhlNOjBnP+noLtsPFqXnWh2L0+nZKCwcu7P7J8k+k+rdIDs+RMnrMwrzhUE4mwMgyN0cYnEn0G8yrnQ==} dependencies: '@aws-crypto/crc32': 5.2.0 '@smithy/types': 3.7.1 '@smithy/util-hex-encoding': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/eventstream-serde-browser@3.0.13': + /@smithy/eventstream-serde-browser@3.0.13: + resolution: {integrity: sha512-Nee9m+97o9Qj6/XeLz2g2vANS2SZgAxV4rDBMKGHvFJHU/xz88x2RwCkwsvEwYjSX4BV1NG1JXmxEaDUzZTAtw==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/eventstream-serde-universal': 3.0.12 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/eventstream-serde-config-resolver@3.0.10': + /@smithy/eventstream-serde-config-resolver@3.0.10: + resolution: {integrity: sha512-K1M0x7P7qbBUKB0UWIL5KOcyi6zqV5mPJoL0/o01HPJr0CSq3A9FYuJC6e11EX6hR8QTIR++DBiGrYveOu6trw==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/eventstream-serde-node@3.0.12': + /@smithy/eventstream-serde-node@3.0.12: + resolution: {integrity: sha512-kiZymxXvZ4tnuYsPSMUHe+MMfc4FTeFWJIc0Q5wygJoUQM4rVHNghvd48y7ppuulNMbuYt95ah71pYc2+o4JOA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/eventstream-serde-universal': 3.0.12 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/eventstream-serde-universal@3.0.12': + /@smithy/eventstream-serde-universal@3.0.12: + resolution: {integrity: sha512-1i8ifhLJrOZ+pEifTlF0EfZzMLUGQggYQ6WmZ4d5g77zEKf7oZ0kvh1yKWHPjofvOwqrkwRDVuxuYC8wVd662A==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/eventstream-codec': 3.1.9 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/fetch-http-handler@4.1.1': + /@smithy/fetch-http-handler@4.1.1: + resolution: {integrity: sha512-bH7QW0+JdX0bPBadXt8GwMof/jz0H28I84hU1Uet9ISpzUqXqRQ3fEZJ+ANPOhzSEczYvANNl3uDQDYArSFDtA==} dependencies: '@smithy/protocol-http': 4.1.7 '@smithy/querystring-builder': 3.0.10 '@smithy/types': 3.7.1 '@smithy/util-base64': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/hash-blob-browser@3.1.9': + /@smithy/hash-blob-browser@3.1.9: + resolution: {integrity: sha512-wOu78omaUuW5DE+PVWXiRKWRZLecARyP3xcq5SmkXUw9+utgN8HnSnBfrjL2B/4ZxgqPjaAJQkC/+JHf1ITVaQ==} dependencies: '@smithy/chunked-blob-reader': 4.0.0 '@smithy/chunked-blob-reader-native': 3.0.1 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/hash-node@3.0.10': + /@smithy/hash-node@3.0.10: + resolution: {integrity: sha512-3zWGWCHI+FlJ5WJwx73Mw2llYR8aflVyZN5JhoqLxbdPZi6UyKSdCeXAWJw9ja22m6S6Tzz1KZ+kAaSwvydi0g==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 '@smithy/util-buffer-from': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/hash-stream-node@3.1.9': + /@smithy/hash-stream-node@3.1.9: + resolution: {integrity: sha512-3XfHBjSP3oDWxLmlxnt+F+FqXpL3WlXs+XXaB6bV9Wo8BBu87fK1dSEsyH7Z4ZHRmwZ4g9lFMdf08m9hoX1iRA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/invalid-dependency@3.0.10': + /@smithy/invalid-dependency@3.0.10: + resolution: {integrity: sha512-Lp2L65vFi+cj0vFMu2obpPW69DU+6O5g3086lmI4XcnRCG8PxvpWC7XyaVwJCxsZFzueHjXnrOH/E0pl0zikfA==} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/is-array-buffer@2.2.0': + /@smithy/is-array-buffer@2.2.0: + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} dependencies: tslib: 2.8.1 + dev: false - '@smithy/is-array-buffer@3.0.0': + /@smithy/is-array-buffer@3.0.0: + resolution: {integrity: sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==} + engines: {node: '>=16.0.0'} dependencies: tslib: 2.8.1 + dev: false - '@smithy/md5-js@3.0.10': + /@smithy/md5-js@3.0.10: + resolution: {integrity: sha512-m3bv6dApflt3fS2Y1PyWPUtRP7iuBlvikEOGwu0HsCZ0vE7zcIX+dBoh3e+31/rddagw8nj92j0kJg2TfV+SJA==} dependencies: '@smithy/types': 3.7.1 '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/middleware-content-length@3.0.12': + /@smithy/middleware-content-length@3.0.12: + resolution: {integrity: sha512-1mDEXqzM20yywaMDuf5o9ue8OkJ373lSPbaSjyEvkWdqELhFMyNNgKGWL/rCSf4KME8B+HlHKuR8u9kRj8HzEQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/protocol-http': 4.1.7 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/middleware-endpoint@3.2.3': + /@smithy/middleware-endpoint@3.2.3: + resolution: {integrity: sha512-Hdl9296i/EMptaX7agrSzJZDiz5Y8XPUeBbctTmMtnCguGpqfU3jVsTUan0VLaOhsnquqWLL8Bl5HrlbVGT1og==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/core': 2.5.3 '@smithy/middleware-serde': 3.0.10 @@ -4968,8 +2036,11 @@ snapshots: '@smithy/url-parser': 3.0.10 '@smithy/util-middleware': 3.0.10 tslib: 2.8.1 + dev: false - '@smithy/middleware-retry@3.0.27': + /@smithy/middleware-retry@3.0.27: + resolution: {integrity: sha512-H3J/PjJpLL7Tt+fxDKiOD25sMc94YetlQhCnYeNmina2LZscAdu0ZEZPas/kwePHABaEtqp7hqa5S4UJgMs1Tg==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/node-config-provider': 3.1.11 '@smithy/protocol-http': 4.1.7 @@ -4980,63 +2051,96 @@ snapshots: '@smithy/util-retry': 3.0.10 tslib: 2.8.1 uuid: 9.0.1 + dev: false - '@smithy/middleware-serde@3.0.10': + /@smithy/middleware-serde@3.0.10: + resolution: {integrity: sha512-MnAuhh+dD14F428ubSJuRnmRsfOpxSzvRhaGVTvd/lrUDE3kxzCCmH8lnVTvoNQnV2BbJ4c15QwZ3UdQBtFNZA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/middleware-stack@3.0.10': + /@smithy/middleware-stack@3.0.10: + resolution: {integrity: sha512-grCHyoiARDBBGPyw2BeicpjgpsDFWZZxptbVKb3CRd/ZA15F/T6rZjCCuBUjJwdck1nwUuIxYtsS4H9DDpbP5w==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/node-config-provider@3.1.11': + /@smithy/node-config-provider@3.1.11: + resolution: {integrity: sha512-URq3gT3RpDikh/8MBJUB+QGZzfS7Bm6TQTqoh4CqE8NBuyPkWa5eUXj0XFcFfeZVgg3WMh1u19iaXn8FvvXxZw==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/property-provider': 3.1.10 '@smithy/shared-ini-file-loader': 3.1.11 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/node-http-handler@3.3.1': + /@smithy/node-http-handler@3.3.1: + resolution: {integrity: sha512-fr+UAOMGWh6bn4YSEezBCpJn9Ukp9oR4D32sCjCo7U81evE11YePOQ58ogzyfgmjIO79YeOdfXXqr0jyhPQeMg==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/abort-controller': 3.1.8 '@smithy/protocol-http': 4.1.7 '@smithy/querystring-builder': 3.0.10 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/property-provider@3.1.10': + /@smithy/property-provider@3.1.10: + resolution: {integrity: sha512-n1MJZGTorTH2DvyTVj+3wXnd4CzjJxyXeOgnTlgNVFxaaMeT4OteEp4QrzF8p9ee2yg42nvyVK6R/awLCakjeQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/protocol-http@4.1.7': + /@smithy/protocol-http@4.1.7: + resolution: {integrity: sha512-FP2LepWD0eJeOTm0SjssPcgqAlDFzOmRXqXmGhfIM52G7Lrox/pcpQf6RP4F21k0+O12zaqQt5fCDOeBtqY6Cg==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/querystring-builder@3.0.10': + /@smithy/querystring-builder@3.0.10: + resolution: {integrity: sha512-nT9CQF3EIJtIUepXQuBFb8dxJi3WVZS3XfuDksxSCSn+/CzZowRLdhDn+2acbBv8R6eaJqPupoI/aRFIImNVPQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 '@smithy/util-uri-escape': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/querystring-parser@3.0.10': + /@smithy/querystring-parser@3.0.10: + resolution: {integrity: sha512-Oa0XDcpo9SmjhiDD9ua2UyM3uU01ZTuIrNdZvzwUTykW1PM8o2yJvMh1Do1rY5sUQg4NDV70dMi0JhDx4GyxuQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/service-error-classification@3.0.10': + /@smithy/service-error-classification@3.0.10: + resolution: {integrity: sha512-zHe642KCqDxXLuhs6xmHVgRwy078RfqxP2wRDpIyiF8EmsWXptMwnMwbVa50lw+WOGNrYm9zbaEg0oDe3PTtvQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 + dev: false - '@smithy/shared-ini-file-loader@3.1.11': + /@smithy/shared-ini-file-loader@3.1.11: + resolution: {integrity: sha512-AUdrIZHFtUgmfSN4Gq9nHu3IkHMa1YDcN+s061Nfm+6pQ0mJy85YQDB0tZBCmls0Vuj22pLwDPmL92+Hvfwwlg==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/signature-v4@4.2.3': + /@smithy/signature-v4@4.2.3: + resolution: {integrity: sha512-pPSQQ2v2vu9vc8iew7sszLd0O09I5TRc5zhY71KA+Ao0xYazIG+uLeHbTJfIWGO3BGVLiXjUr3EEeCcEQLjpWQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/is-array-buffer': 3.0.0 '@smithy/protocol-http': 4.1.7 @@ -5046,8 +2150,11 @@ snapshots: '@smithy/util-uri-escape': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/smithy-client@3.4.4': + /@smithy/smithy-client@3.4.4: + resolution: {integrity: sha512-dPGoJuSZqvirBq+yROapBcHHvFjChoAQT8YPWJ820aPHHiowBlB3RL1Q4kPT1hx0qKgJuf+HhyzKi5Gbof4fNA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/core': 2.5.3 '@smithy/middleware-endpoint': 3.2.3 @@ -5056,54 +2163,82 @@ snapshots: '@smithy/types': 3.7.1 '@smithy/util-stream': 3.3.1 tslib: 2.8.1 + dev: false - '@smithy/types@3.7.1': + /@smithy/types@3.7.1: + resolution: {integrity: sha512-XKLcLXZY7sUQgvvWyeaL/qwNPp6V3dWcUjqrQKjSb+tzYiCy340R/c64LV5j+Tnb2GhmunEX0eou+L+m2hJNYA==} + engines: {node: '>=16.0.0'} dependencies: tslib: 2.8.1 + dev: false - '@smithy/url-parser@3.0.10': + /@smithy/url-parser@3.0.10: + resolution: {integrity: sha512-j90NUalTSBR2NaZTuruEgavSdh8MLirf58LoGSk4AtQfyIymogIhgnGUU2Mga2bkMkpSoC9gxb74xBXL5afKAQ==} dependencies: '@smithy/querystring-parser': 3.0.10 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/util-base64@3.0.0': + /@smithy/util-base64@3.0.0: + resolution: {integrity: sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/util-buffer-from': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/util-body-length-browser@3.0.0': + /@smithy/util-body-length-browser@3.0.0: + resolution: {integrity: sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==} dependencies: tslib: 2.8.1 + dev: false - '@smithy/util-body-length-node@3.0.0': + /@smithy/util-body-length-node@3.0.0: + resolution: {integrity: sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==} + engines: {node: '>=16.0.0'} dependencies: tslib: 2.8.1 + dev: false - '@smithy/util-buffer-from@2.2.0': + /@smithy/util-buffer-from@2.2.0: + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} dependencies: '@smithy/is-array-buffer': 2.2.0 tslib: 2.8.1 + dev: false - '@smithy/util-buffer-from@3.0.0': + /@smithy/util-buffer-from@3.0.0: + resolution: {integrity: sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/is-array-buffer': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/util-config-provider@3.0.0': + /@smithy/util-config-provider@3.0.0: + resolution: {integrity: sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==} + engines: {node: '>=16.0.0'} dependencies: tslib: 2.8.1 + dev: false - '@smithy/util-defaults-mode-browser@3.0.27': + /@smithy/util-defaults-mode-browser@3.0.27: + resolution: {integrity: sha512-GV8NvPy1vAGp7u5iD/xNKUxCorE4nQzlyl057qRac+KwpH5zq8wVq6rE3lPPeuFLyQXofPN6JwxL1N9ojGapiQ==} + engines: {node: '>= 10.0.0'} dependencies: '@smithy/property-provider': 3.1.10 '@smithy/smithy-client': 3.4.4 '@smithy/types': 3.7.1 bowser: 2.11.0 tslib: 2.8.1 + dev: false - '@smithy/util-defaults-mode-node@3.0.27': + /@smithy/util-defaults-mode-node@3.0.27: + resolution: {integrity: sha512-7+4wjWfZqZxZVJvDutO+i1GvL6bgOajEkop4FuR6wudFlqBiqwxw3HoH6M9NgeCd37km8ga8NPp2JacQEtAMPg==} + engines: {node: '>= 10.0.0'} dependencies: '@smithy/config-resolver': 3.0.12 '@smithy/credential-provider-imds': 3.2.7 @@ -5112,29 +2247,44 @@ snapshots: '@smithy/smithy-client': 3.4.4 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/util-endpoints@2.1.6': + /@smithy/util-endpoints@2.1.6: + resolution: {integrity: sha512-mFV1t3ndBh0yZOJgWxO9J/4cHZVn5UG1D8DeCc6/echfNkeEJWu9LD7mgGH5fHrEdR7LDoWw7PQO6QiGpHXhgA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/node-config-provider': 3.1.11 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/util-hex-encoding@3.0.0': + /@smithy/util-hex-encoding@3.0.0: + resolution: {integrity: sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==} + engines: {node: '>=16.0.0'} dependencies: tslib: 2.8.1 + dev: false - '@smithy/util-middleware@3.0.10': + /@smithy/util-middleware@3.0.10: + resolution: {integrity: sha512-eJO+/+RsrG2RpmY68jZdwQtnfsxjmPxzMlQpnHKjFPwrYqvlcT+fHdT+ZVwcjlWSrByOhGr9Ff2GG17efc192A==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/util-retry@3.0.10': + /@smithy/util-retry@3.0.10: + resolution: {integrity: sha512-1l4qatFp4PiU6j7UsbasUHL2VU023NRB/gfaa1M0rDqVrRN4g3mCArLRyH3OuktApA4ye+yjWQHjdziunw2eWA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/service-error-classification': 3.0.10 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@smithy/util-stream@3.3.1': + /@smithy/util-stream@3.3.1: + resolution: {integrity: sha512-Ff68R5lJh2zj+AUTvbAU/4yx+6QPRzg7+pI7M1FbtQHcRIp7xvguxVsQBKyB3fwiOwhAKu0lnNyYBaQfSW6TNw==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/fetch-http-handler': 4.1.1 '@smithy/node-http-handler': 3.3.1 @@ -5144,173 +2294,269 @@ snapshots: '@smithy/util-hex-encoding': 3.0.0 '@smithy/util-utf8': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/util-uri-escape@3.0.0': + /@smithy/util-uri-escape@3.0.0: + resolution: {integrity: sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==} + engines: {node: '>=16.0.0'} dependencies: tslib: 2.8.1 + dev: false - '@smithy/util-utf8@2.3.0': + /@smithy/util-utf8@2.3.0: + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} dependencies: '@smithy/util-buffer-from': 2.2.0 tslib: 2.8.1 + dev: false - '@smithy/util-utf8@3.0.0': + /@smithy/util-utf8@3.0.0: + resolution: {integrity: sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/util-buffer-from': 3.0.0 tslib: 2.8.1 + dev: false - '@smithy/util-waiter@3.1.9': + /@smithy/util-waiter@3.1.9: + resolution: {integrity: sha512-/aMXPANhMOlMPjfPtSrDfPeVP8l56SJlz93xeiLmhLe5xvlXA5T3abZ2ilEsDEPeY9T/wnN/vNGn9wa1SbufWA==} + engines: {node: '>=16.0.0'} dependencies: '@smithy/abort-controller': 3.1.8 '@smithy/types': 3.7.1 tslib: 2.8.1 + dev: false - '@sqltools/formatter@1.2.5': {} + /@sqltools/formatter@1.2.5: + resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} + dev: false - '@tsconfig/node10@1.0.11': {} + /@tsconfig/node10@1.0.11: + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} - '@tsconfig/node12@1.0.11': {} + /@tsconfig/node12@1.0.11: + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - '@tsconfig/node14@1.0.3': {} + /@tsconfig/node14@1.0.3: + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - '@tsconfig/node16@1.0.4': {} + /@tsconfig/node16@1.0.4: + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - '@types/babel__core@7.20.5': + /@types/babel__core@7.20.5: + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} dependencies: '@babel/parser': 7.26.2 '@babel/types': 7.26.0 '@types/babel__generator': 7.6.8 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.20.6 + dev: true - '@types/babel__generator@7.6.8': + /@types/babel__generator@7.6.8: + resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} dependencies: '@babel/types': 7.26.0 + dev: true - '@types/babel__template@7.4.4': + /@types/babel__template@7.4.4: + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} dependencies: '@babel/parser': 7.26.2 '@babel/types': 7.26.0 + dev: true - '@types/babel__traverse@7.20.6': + /@types/babel__traverse@7.20.6: + resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} dependencies: '@babel/types': 7.26.0 + dev: true - '@types/body-parser@1.19.5': + /@types/body-parser@1.19.5: + resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} dependencies: '@types/connect': 3.4.38 '@types/node': 20.3.1 + dev: true - '@types/connect@3.4.38': + /@types/connect@3.4.38: + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} dependencies: '@types/node': 20.3.1 + dev: true - '@types/cookiejar@2.1.5': {} + /@types/cookiejar@2.1.5: + resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==} + dev: true - '@types/eslint-scope@3.7.7': + /@types/eslint-scope@3.7.7: + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} dependencies: '@types/eslint': 9.6.1 '@types/estree': 1.0.6 + dev: true - '@types/eslint@9.6.1': + /@types/eslint@9.6.1: + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} dependencies: '@types/estree': 1.0.6 '@types/json-schema': 7.0.15 + dev: true - '@types/estree@1.0.6': {} + /@types/estree@1.0.6: + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + dev: true - '@types/express-serve-static-core@5.0.1': + /@types/express-serve-static-core@5.0.1: + resolution: {integrity: sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==} dependencies: '@types/node': 20.3.1 '@types/qs': 6.9.17 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 + dev: true - '@types/express@5.0.0': + /@types/express@5.0.0: + resolution: {integrity: sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==} dependencies: '@types/body-parser': 1.19.5 '@types/express-serve-static-core': 5.0.1 '@types/qs': 6.9.17 '@types/serve-static': 1.15.7 + dev: true - '@types/graceful-fs@4.1.9': + /@types/graceful-fs@4.1.9: + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} dependencies: '@types/node': 20.3.1 + dev: true - '@types/http-errors@2.0.4': {} + /@types/http-errors@2.0.4: + resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} + dev: true - '@types/istanbul-lib-coverage@2.0.6': {} + /@types/istanbul-lib-coverage@2.0.6: + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + dev: true - '@types/istanbul-lib-report@3.0.3': + /@types/istanbul-lib-report@3.0.3: + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} dependencies: '@types/istanbul-lib-coverage': 2.0.6 + dev: true - '@types/istanbul-reports@3.0.4': + /@types/istanbul-reports@3.0.4: + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} dependencies: '@types/istanbul-lib-report': 3.0.3 + dev: true - '@types/jest@29.5.2': + /@types/jest@29.5.2: + resolution: {integrity: sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg==} dependencies: expect: 29.7.0 pretty-format: 29.7.0 + dev: true - '@types/json-schema@7.0.15': {} + /@types/json-schema@7.0.15: + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + dev: true - '@types/jsonwebtoken@9.0.5': + /@types/jsonwebtoken@9.0.5: + resolution: {integrity: sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==} dependencies: '@types/node': 20.3.1 + dev: false - '@types/methods@1.1.4': {} + /@types/methods@1.1.4: + resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==} + dev: true - '@types/mime@1.3.5': {} + /@types/mime@1.3.5: + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + dev: true - '@types/multer@1.4.12': + /@types/multer@1.4.12: + resolution: {integrity: sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==} dependencies: '@types/express': 5.0.0 + dev: true - '@types/node@20.3.1': {} + /@types/node@20.3.1: + resolution: {integrity: sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==} - '@types/parse-json@4.0.2': {} + /@types/parse-json@4.0.2: + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + dev: true - '@types/qs@6.9.17': {} + /@types/qs@6.9.17: + resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==} + dev: true - '@types/range-parser@1.2.7': {} + /@types/range-parser@1.2.7: + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + dev: true - '@types/send@0.17.4': + /@types/send@0.17.4: + resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} dependencies: '@types/mime': 1.3.5 '@types/node': 20.3.1 + dev: true - '@types/serve-static@1.15.7': + /@types/serve-static@1.15.7: + resolution: {integrity: sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==} dependencies: '@types/http-errors': 2.0.4 '@types/node': 20.3.1 '@types/send': 0.17.4 + dev: true - '@types/stack-utils@2.0.3': {} + /@types/stack-utils@2.0.3: + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + dev: true - '@types/superagent@8.1.9': + /@types/superagent@8.1.9: + resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==} dependencies: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 '@types/node': 20.3.1 form-data: 4.0.1 + dev: true - '@types/supertest@6.0.0': + /@types/supertest@6.0.0: + resolution: {integrity: sha512-j3/Z2avY+H3yn+xp/ef//QyqqE+dg3rWh14Ewi/QZs6uVK+oOs7lFRXtjp2YHAqHJZ4OFGNmCxZO5vd7AuG/Dg==} dependencies: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 '@types/superagent': 8.1.9 + dev: true - '@types/validator@13.12.2': {} + /@types/validator@13.12.2: + resolution: {integrity: sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==} - '@types/yargs-parser@21.0.3': {} + /@types/yargs-parser@21.0.3: + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + dev: true - '@types/yargs@17.0.33': + /@types/yargs@17.0.33: + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} dependencies: '@types/yargs-parser': 21.0.3 + dev: true - '@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0(eslint@8.0.0)(typescript@5.1.3))(eslint@8.0.0)(typescript@5.1.3)': + /@typescript-eslint/eslint-plugin@8.0.0(@typescript-eslint/parser@8.0.0)(eslint@8.0.0)(typescript@5.1.3): + resolution: {integrity: sha512-STIZdwEQRXAHvNUS6ILDf5z3u95Gc8jzywunxSNqX00OooIemaaNIA0vEgynJlycL5AjabYLLrIyHd4iazyvtg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true dependencies: '@eslint-community/regexpp': 4.12.1 '@typescript-eslint/parser': 8.0.0(eslint@8.0.0)(typescript@5.1.3) @@ -5323,12 +2569,20 @@ snapshots: ignore: 5.3.2 natural-compare: 1.4.0 ts-api-utils: 1.4.0(typescript@5.1.3) - optionalDependencies: typescript: 5.1.3 transitivePeerDependencies: - supports-color + dev: true - '@typescript-eslint/parser@8.0.0(eslint@8.0.0)(typescript@5.1.3)': + /@typescript-eslint/parser@8.0.0(eslint@8.0.0)(typescript@5.1.3): + resolution: {integrity: sha512-pS1hdZ+vnrpDIxuFXYQpLTILglTjSYJ9MbetZctrUawogUsPdz31DIIRZ9+rab0LhYNTsk88w4fIzVheiTbWOQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true dependencies: '@typescript-eslint/scope-manager': 8.0.0 '@typescript-eslint/types': 8.0.0 @@ -5336,31 +2590,51 @@ snapshots: '@typescript-eslint/visitor-keys': 8.0.0 debug: 4.3.7 eslint: 8.0.0 - optionalDependencies: typescript: 5.1.3 transitivePeerDependencies: - supports-color + dev: true - '@typescript-eslint/scope-manager@8.0.0': + /@typescript-eslint/scope-manager@8.0.0: + resolution: {integrity: sha512-V0aa9Csx/ZWWv2IPgTfY7T4agYwJyILESu/PVqFtTFz9RIS823mAze+NbnBI8xiwdX3iqeQbcTYlvB04G9wyQw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: '@typescript-eslint/types': 8.0.0 '@typescript-eslint/visitor-keys': 8.0.0 + dev: true - '@typescript-eslint/type-utils@8.0.0(eslint@8.0.0)(typescript@5.1.3)': + /@typescript-eslint/type-utils@8.0.0(eslint@8.0.0)(typescript@5.1.3): + resolution: {integrity: sha512-mJAFP2mZLTBwAn5WI4PMakpywfWFH5nQZezUQdSKV23Pqo6o9iShQg1hP2+0hJJXP2LnZkWPphdIq4juYYwCeg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true dependencies: '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.1.3) '@typescript-eslint/utils': 8.0.0(eslint@8.0.0)(typescript@5.1.3) debug: 4.3.7 ts-api-utils: 1.4.0(typescript@5.1.3) - optionalDependencies: typescript: 5.1.3 transitivePeerDependencies: - eslint - supports-color + dev: true - '@typescript-eslint/types@8.0.0': {} + /@typescript-eslint/types@8.0.0: + resolution: {integrity: sha512-wgdSGs9BTMWQ7ooeHtu5quddKKs5Z5dS+fHLbrQI+ID0XWJLODGMHRfhwImiHoeO2S5Wir2yXuadJN6/l4JRxw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: true - '@typescript-eslint/typescript-estree@8.0.0(typescript@5.1.3)': + /@typescript-eslint/typescript-estree@8.0.0(typescript@5.1.3): + resolution: {integrity: sha512-5b97WpKMX+Y43YKi4zVcCVLtK5F98dFls3Oxui8LbnmRsseKenbbDinmvxrWegKDMmlkIq/XHuyy0UGLtpCDKg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true dependencies: '@typescript-eslint/types': 8.0.0 '@typescript-eslint/visitor-keys': 8.0.0 @@ -5370,12 +2644,16 @@ snapshots: minimatch: 9.0.5 semver: 7.6.3 ts-api-utils: 1.4.0(typescript@5.1.3) - optionalDependencies: typescript: 5.1.3 transitivePeerDependencies: - supports-color + dev: true - '@typescript-eslint/utils@8.0.0(eslint@8.0.0)(typescript@5.1.3)': + /@typescript-eslint/utils@8.0.0(eslint@8.0.0)(typescript@5.1.3): + resolution: {integrity: sha512-k/oS/A/3QeGLRvOWCg6/9rATJL5rec7/5s1YmdS0ZU6LHveJyGFwBvLhSRBv6i9xaj7etmosp+l+ViN1I9Aj/Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 dependencies: '@eslint-community/eslint-utils': 4.4.1(eslint@8.0.0) '@typescript-eslint/scope-manager': 8.0.0 @@ -5385,49 +2663,74 @@ snapshots: transitivePeerDependencies: - supports-color - typescript + dev: true - '@typescript-eslint/visitor-keys@8.0.0': + /@typescript-eslint/visitor-keys@8.0.0: + resolution: {integrity: sha512-oN0K4nkHuOyF3PVMyETbpP5zp6wfyOvm7tWhTMfoqxSSsPmJIh6JNASuZDlODE8eE+0EB9uar+6+vxr9DBTYOA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} dependencies: '@typescript-eslint/types': 8.0.0 eslint-visitor-keys: 3.4.3 + dev: true - '@webassemblyjs/ast@1.14.1': + /@webassemblyjs/ast@1.14.1: + resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} dependencies: '@webassemblyjs/helper-numbers': 1.13.2 '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + dev: true - '@webassemblyjs/floating-point-hex-parser@1.13.2': {} + /@webassemblyjs/floating-point-hex-parser@1.13.2: + resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + dev: true - '@webassemblyjs/helper-api-error@1.13.2': {} + /@webassemblyjs/helper-api-error@1.13.2: + resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + dev: true - '@webassemblyjs/helper-buffer@1.14.1': {} + /@webassemblyjs/helper-buffer@1.14.1: + resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + dev: true - '@webassemblyjs/helper-numbers@1.13.2': + /@webassemblyjs/helper-numbers@1.13.2: + resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} dependencies: '@webassemblyjs/floating-point-hex-parser': 1.13.2 '@webassemblyjs/helper-api-error': 1.13.2 '@xtuc/long': 4.2.2 + dev: true - '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} + /@webassemblyjs/helper-wasm-bytecode@1.13.2: + resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + dev: true - '@webassemblyjs/helper-wasm-section@1.14.1': + /@webassemblyjs/helper-wasm-section@1.14.1: + resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} dependencies: '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/helper-buffer': 1.14.1 '@webassemblyjs/helper-wasm-bytecode': 1.13.2 '@webassemblyjs/wasm-gen': 1.14.1 + dev: true - '@webassemblyjs/ieee754@1.13.2': + /@webassemblyjs/ieee754@1.13.2: + resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} dependencies: '@xtuc/ieee754': 1.2.0 + dev: true - '@webassemblyjs/leb128@1.13.2': + /@webassemblyjs/leb128@1.13.2: + resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} dependencies: '@xtuc/long': 4.2.2 + dev: true - '@webassemblyjs/utf8@1.13.2': {} + /@webassemblyjs/utf8@1.13.2: + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + dev: true - '@webassemblyjs/wasm-edit@1.14.1': + /@webassemblyjs/wasm-edit@1.14.1: + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} dependencies: '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/helper-buffer': 1.14.1 @@ -5437,23 +2740,29 @@ snapshots: '@webassemblyjs/wasm-opt': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 '@webassemblyjs/wast-printer': 1.14.1 + dev: true - '@webassemblyjs/wasm-gen@1.14.1': + /@webassemblyjs/wasm-gen@1.14.1: + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} dependencies: '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/helper-wasm-bytecode': 1.13.2 '@webassemblyjs/ieee754': 1.13.2 '@webassemblyjs/leb128': 1.13.2 '@webassemblyjs/utf8': 1.13.2 + dev: true - '@webassemblyjs/wasm-opt@1.14.1': + /@webassemblyjs/wasm-opt@1.14.1: + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} dependencies: '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/helper-buffer': 1.14.1 '@webassemblyjs/wasm-gen': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 + dev: true - '@webassemblyjs/wasm-parser@1.14.1': + /@webassemblyjs/wasm-parser@1.14.1: + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} dependencies: '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/helper-api-error': 1.13.2 @@ -5461,121 +2770,213 @@ snapshots: '@webassemblyjs/ieee754': 1.13.2 '@webassemblyjs/leb128': 1.13.2 '@webassemblyjs/utf8': 1.13.2 + dev: true - '@webassemblyjs/wast-printer@1.14.1': + /@webassemblyjs/wast-printer@1.14.1: + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} dependencies: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 + dev: true - '@xtuc/ieee754@1.2.0': {} + /@xtuc/ieee754@1.2.0: + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + dev: true - '@xtuc/long@4.2.2': {} + /@xtuc/long@4.2.2: + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + dev: true - accepts@1.3.8: + /accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} dependencies: mime-types: 2.1.35 negotiator: 0.6.3 - acorn-import-assertions@1.9.0(acorn@8.14.0): + /acorn-import-assertions@1.9.0(acorn@8.14.0): + resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} + deprecated: package has been renamed to acorn-import-attributes + peerDependencies: + acorn: ^8 dependencies: acorn: 8.14.0 + dev: true - acorn-jsx@5.3.2(acorn@8.14.0): + /acorn-jsx@5.3.2(acorn@8.14.0): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: acorn: 8.14.0 + dev: true - acorn-walk@8.3.4: + /acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} dependencies: acorn: 8.14.0 - acorn@8.14.0: {} + /acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + engines: {node: '>=0.4.0'} + hasBin: true - ajv-formats@2.1.1(ajv@8.12.0): - optionalDependencies: + /ajv-formats@2.1.1(ajv@8.12.0): + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + dependencies: ajv: 8.12.0 + dev: true - ajv-keywords@3.5.2(ajv@6.12.6): + /ajv-keywords@3.5.2(ajv@6.12.6): + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 dependencies: ajv: 6.12.6 + dev: true - ajv@6.12.6: + /ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 + dev: true - ajv@8.12.0: + /ajv@8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} dependencies: fast-deep-equal: 3.1.3 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 uri-js: 4.4.1 + dev: true - ansi-colors@4.1.3: {} + /ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + dev: true - ansi-escapes@4.3.2: + /ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} dependencies: type-fest: 0.21.3 + dev: true - ansi-regex@5.0.1: {} + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} - ansi-regex@6.1.0: {} + /ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} + engines: {node: '>=12'} + dev: false - ansi-styles@4.3.0: + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} dependencies: color-convert: 2.0.1 - ansi-styles@5.2.0: {} + /ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + dev: true - ansi-styles@6.2.1: {} + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + dev: false - any-promise@1.3.0: {} + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: false - anymatch@3.1.3: + /anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 + dev: true - app-root-path@3.1.0: {} + /app-root-path@3.1.0: + resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} + engines: {node: '>= 6.0.0'} + dev: false - append-field@1.0.0: {} + /append-field@1.0.0: + resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} - arg@4.1.3: {} + /arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - argon2@0.41.1: + /argon2@0.41.1: + resolution: {integrity: sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==} + engines: {node: '>=16.17.0'} + requiresBuild: true dependencies: '@phc/format': 1.0.0 node-addon-api: 8.2.2 node-gyp-build: 4.8.2 + dev: false - argparse@1.0.10: + /argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} dependencies: sprintf-js: 1.0.3 + dev: true - argparse@2.0.1: {} + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - array-flatten@1.1.1: {} + /array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - array-timsort@1.0.3: {} + /array-timsort@1.0.3: + resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + dev: true - array-union@2.1.0: {} + /array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + dev: true - asap@2.0.6: {} + /asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + dev: true - asynckit@0.4.0: {} + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - aws-ssl-profiles@1.1.2: {} + /aws-ssl-profiles@1.1.2: + resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==} + engines: {node: '>= 6.0.0'} + dev: false - axios@1.7.7: + /axios@1.7.7: + resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} dependencies: follow-redirects: 1.15.9 form-data: 4.0.1 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug + dev: false - babel-jest@29.7.0(@babel/core@7.26.0): + /babel-jest@29.7.0(@babel/core@7.26.0): + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 dependencies: '@babel/core': 7.26.0 '@jest/transform': 29.7.0 @@ -5587,8 +2988,11 @@ snapshots: slash: 3.0.0 transitivePeerDependencies: - supports-color + dev: true - babel-plugin-istanbul@6.1.1: + /babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} dependencies: '@babel/helper-plugin-utils': 7.25.9 '@istanbuljs/load-nyc-config': 1.1.0 @@ -5597,15 +3001,22 @@ snapshots: test-exclude: 6.0.0 transitivePeerDependencies: - supports-color + dev: true - babel-plugin-jest-hoist@29.6.3: + /babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/template': 7.25.9 '@babel/types': 7.26.0 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.6 + dev: true - babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.0): + /babel-preset-current-node-syntax@1.1.0(@babel/core@7.26.0): + resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} + peerDependencies: + '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.26.0 '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.26.0) @@ -5623,26 +3034,41 @@ snapshots: '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.26.0) '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.26.0) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.26.0) + dev: true - babel-preset-jest@29.6.3(@babel/core@7.26.0): + /babel-preset-jest@29.6.3(@babel/core@7.26.0): + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 dependencies: '@babel/core': 7.26.0 babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.0) + dev: true - balanced-match@1.0.2: {} + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - base64-js@1.5.1: {} + /base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - binary-extensions@2.3.0: {} + /binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + dev: true - bl@4.1.0: + /bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} dependencies: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 + dev: true - body-parser@1.20.1: + /body-parser@1.20.1: + resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} dependencies: bytes: 3.1.2 content-type: 1.0.5 @@ -5659,7 +3085,9 @@ snapshots: transitivePeerDependencies: - supports-color - body-parser@1.20.2: + /body-parser@1.20.2: + resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} dependencies: bytes: 3.1.2 content-type: 1.0.5 @@ -5676,57 +3104,86 @@ snapshots: transitivePeerDependencies: - supports-color - bowser@2.11.0: {} + /bowser@2.11.0: + resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + dev: false - brace-expansion@1.1.11: + /brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 + dev: true - brace-expansion@2.0.1: + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 - braces@3.0.3: + /braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} dependencies: fill-range: 7.1.1 - browserslist@4.24.2: + /browserslist@4.24.2: + resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true dependencies: caniuse-lite: 1.0.30001679 electron-to-chromium: 1.5.55 node-releases: 2.0.18 update-browserslist-db: 1.1.1(browserslist@4.24.2) + dev: true - bs-logger@0.2.6: + /bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} dependencies: fast-json-stable-stringify: 2.1.0 + dev: true - bser@2.1.1: + /bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} dependencies: node-int64: 0.4.0 + dev: true - buffer-equal-constant-time@1.0.1: {} + /buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + dev: false - buffer-from@1.1.2: {} + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - buffer@5.7.1: + /buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + dev: true - buffer@6.0.3: + /buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + dev: false - busboy@1.6.0: + /busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} dependencies: streamsearch: 1.1.0 - bytes@3.1.2: {} + /bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} - call-bind@1.0.7: + /call-bind@1.0.7: + resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + engines: {node: '>= 0.4'} dependencies: es-define-property: 1.0.0 es-errors: 1.3.0 @@ -5734,24 +3191,44 @@ snapshots: get-intrinsic: 1.2.4 set-function-length: 1.2.2 - callsites@3.1.0: {} + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: true - camelcase@5.3.1: {} + /camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + dev: true - camelcase@6.3.0: {} + /camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + dev: true - caniuse-lite@1.0.30001679: {} + /caniuse-lite@1.0.30001679: + resolution: {integrity: sha512-j2YqID/YwpLnKzCmBOS4tlZdWprXm3ZmQLBH9ZBXFOhoxLA46fwyBvx6toCBWBmnuwUY/qB3kEU6gFx8qgCroA==} + dev: true - chalk@4.1.2: + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - char-regex@1.0.2: {} + /char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + dev: true - chardet@0.7.0: {} + /chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + dev: true - chokidar@3.5.3: + /chokidar@3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} dependencies: anymatch: 3.1.3 braces: 3.0.3 @@ -5762,26 +3239,43 @@ snapshots: readdirp: 3.6.0 optionalDependencies: fsevents: 2.3.3 + dev: true - chrome-trace-event@1.0.4: {} + /chrome-trace-event@1.0.4: + resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} + engines: {node: '>=6.0'} + dev: true - ci-info@3.9.0: {} + /ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + dev: true - cjs-module-lexer@1.4.1: {} + /cjs-module-lexer@1.4.1: + resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} + dev: true - class-transformer@0.5.1: {} + /class-transformer@0.5.1: + resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} - class-validator@0.14.1: + /class-validator@0.14.1: + resolution: {integrity: sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==} dependencies: '@types/validator': 13.12.2 libphonenumber-js: 1.11.14 validator: 13.12.0 - cli-cursor@3.1.0: + /cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} dependencies: restore-cursor: 3.1.0 + dev: true - cli-highlight@2.1.11: + /cli-highlight@2.1.11: + resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true dependencies: chalk: 4.1.2 highlight.js: 10.7.3 @@ -5789,108 +3283,173 @@ snapshots: parse5: 5.1.1 parse5-htmlparser2-tree-adapter: 6.0.1 yargs: 16.2.0 + dev: false - cli-spinners@2.9.2: {} + /cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + dev: true - cli-table3@0.6.3: + /cli-table3@0.6.3: + resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==} + engines: {node: 10.* || >= 12.*} dependencies: string-width: 4.2.3 optionalDependencies: '@colors/colors': 1.5.0 + dev: true - cli-width@3.0.0: {} + /cli-width@3.0.0: + resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==} + engines: {node: '>= 10'} + dev: true - cliui@7.0.4: + /cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + dev: false - cliui@8.0.1: + /cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - clone@1.0.4: {} + /clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + dev: true - co@4.6.0: {} + /co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + dev: true - collect-v8-coverage@1.0.2: {} + /collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + dev: true - color-convert@2.0.1: + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 - color-name@1.1.4: {} + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - combined-stream@1.0.8: + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} dependencies: delayed-stream: 1.0.0 - commander@2.20.3: {} + /commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + dev: true - commander@4.1.1: {} + /commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + dev: true - comment-json@4.2.3: + /comment-json@4.2.3: + resolution: {integrity: sha512-SsxdiOf064DWoZLH799Ata6u7iV658A11PlWtZATDlXPpKGJnbJZ5Z24ybixAi+LUUqJ/GKowAejtC5GFUG7Tw==} + engines: {node: '>= 6'} dependencies: array-timsort: 1.0.3 core-util-is: 1.0.3 esprima: 4.0.1 has-own-prop: 2.0.0 repeat-string: 1.6.1 + dev: true - component-emitter@1.3.1: {} + /component-emitter@1.3.1: + resolution: {integrity: sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==} + dev: true - concat-map@0.0.1: {} + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: true - concat-stream@1.6.2: + /concat-stream@1.6.2: + resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} + engines: {'0': node >= 0.8} dependencies: buffer-from: 1.1.2 inherits: 2.0.4 readable-stream: 2.3.8 typedarray: 0.0.6 - consola@2.15.3: {} + /consola@2.15.3: + resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} - consola@3.2.3: {} + /consola@3.2.3: + resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} + engines: {node: ^14.18.0 || >=16.10.0} + dev: false - content-disposition@0.5.4: + /content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} dependencies: safe-buffer: 5.2.1 - content-type@1.0.5: {} + /content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} - convert-source-map@2.0.0: {} + /convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + dev: true - cookie-signature@1.0.6: {} + /cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - cookie@0.5.0: {} + /cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} - cookiejar@2.1.4: {} + /cookiejar@2.1.4: + resolution: {integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==} + dev: true - core-util-is@1.0.3: {} + /core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - cors@2.8.5: + /cors@2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} dependencies: object-assign: 4.1.1 vary: 1.1.2 - cosmiconfig@7.1.0: + /cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} dependencies: '@types/parse-json': 4.0.2 import-fresh: 3.3.0 parse-json: 5.2.0 path-type: 4.0.0 yaml: 1.10.2 + dev: true - create-jest@29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): + /create-jest@29.7.0(@types/node@20.3.1)(ts-node@10.9.1): + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) + jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -5898,172 +3457,326 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + dev: true - create-require@1.1.1: {} + /create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - cross-spawn@7.0.5: + /cross-spawn@7.0.5: + resolution: {integrity: sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==} + engines: {node: '>= 8'} dependencies: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - dayjs@1.11.13: {} + /dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + dev: false - debug@2.6.9: + /debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true dependencies: ms: 2.0.0 - debug@4.3.7: + /debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true dependencies: ms: 2.1.3 - dedent@1.5.3: {} + /dedent@1.5.3: + resolution: {integrity: sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + dev: true - deep-is@0.1.4: {} + /deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: true - deepmerge@4.3.1: {} + /deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + dev: true - defaults@1.0.4: + /defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} dependencies: clone: 1.0.4 + dev: true - define-data-property@1.1.4: + /define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} dependencies: es-define-property: 1.0.0 es-errors: 1.3.0 gopd: 1.0.1 - delayed-stream@1.0.0: {} + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} - denque@2.1.0: {} + /denque@2.1.0: + resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} + engines: {node: '>=0.10'} + dev: false - depd@2.0.0: {} + /depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} - destr@2.0.3: {} + /destr@2.0.3: + resolution: {integrity: sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==} + dev: false - destroy@1.2.0: {} + /destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - detect-newline@3.1.0: {} + /detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + dev: true - dezalgo@1.0.4: + /dezalgo@1.0.4: + resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} dependencies: asap: 2.0.6 wrappy: 1.0.2 + dev: true - diff-sequences@29.6.3: {} + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true - diff@4.0.2: {} + /diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} - dir-glob@3.0.1: + /dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} dependencies: path-type: 4.0.0 + dev: true - doctrine@3.0.0: + /doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} dependencies: esutils: 2.0.3 + dev: true - dotenv-expand@10.0.0: {} + /dotenv-expand@10.0.0: + resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} + engines: {node: '>=12'} + dev: false - dotenv@16.4.5: {} + /dotenv@16.4.5: + resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} + engines: {node: '>=12'} + dev: false - eastasianwidth@0.2.0: {} + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: false - ebec@1.1.1: + /ebec@1.1.1: + resolution: {integrity: sha512-JZ1vcvPQtR+8LGbZmbjG21IxLQq/v47iheJqn2F6yB2CgnGfn8ZVg3myHrf3buIZS8UCwQK0jOSIb3oHX7aH8g==} dependencies: smob: 1.5.0 + dev: false - ebec@2.3.0: {} + /ebec@2.3.0: + resolution: {integrity: sha512-bt+0tSL7223VU3PSVi0vtNLZ8pO1AfWolcPPMk2a/a5H+o/ZU9ky0n3A0zhrR4qzJTN61uPsGIO4ShhOukdzxA==} + dev: false - ecdsa-sig-formatter@1.0.11: + /ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} dependencies: safe-buffer: 5.2.1 + dev: false - ee-first@1.1.1: {} + /ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.55: {} + /electron-to-chromium@1.5.55: + resolution: {integrity: sha512-6maZ2ASDOTBtjt9FhqYPRnbvKU5tjG0IN9SztUOWYw2AzNDNpKJYLJmlK0/En4Hs/aiWnB+JZ+gW19PIGszgKg==} + dev: true - emittery@0.13.1: {} + /emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + dev: true - emoji-regex@8.0.0: {} + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - emoji-regex@9.2.2: {} + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: false - encodeurl@1.0.2: {} + /encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} - end-of-stream@1.4.4: + /end-of-stream@1.4.4: + resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: once: 1.4.0 + dev: true - enhanced-resolve@5.17.1: + /enhanced-resolve@5.17.1: + resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} + engines: {node: '>=10.13.0'} dependencies: graceful-fs: 4.2.11 tapable: 2.2.1 + dev: true - enquirer@2.4.1: + /enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} dependencies: ansi-colors: 4.1.3 strip-ansi: 6.0.1 + dev: true - envix@1.5.0: + /envix@1.5.0: + resolution: {integrity: sha512-IOxTKT+tffjxgvX2O5nq6enbkv6kBQ/QdMy18bZWo0P0rKPvsRp2/EypIPwTvJfnmk3VdOlq/KcRSZCswefM/w==} + engines: {node: '>=18.0.0'} dependencies: std-env: 3.8.0 + dev: false - error-ex@1.3.2: + /error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: is-arrayish: 0.2.1 + dev: true - es-define-property@1.0.0: + /es-define-property@1.0.0: + resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + engines: {node: '>= 0.4'} dependencies: get-intrinsic: 1.2.4 - es-errors@1.3.0: {} + /es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} - es-module-lexer@1.5.4: {} + /es-module-lexer@1.5.4: + resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + dev: true - escalade@3.2.0: {} + /escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} - escape-html@1.0.3: {} + /escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - escape-string-regexp@1.0.5: {} + /escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + dev: true - escape-string-regexp@2.0.0: {} + /escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + dev: true - escape-string-regexp@4.0.0: {} + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: true - eslint-config-prettier@9.0.0(eslint@8.0.0): + /eslint-config-prettier@9.0.0(eslint@8.0.0): + resolution: {integrity: sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' dependencies: eslint: 8.0.0 + dev: true - eslint-plugin-prettier@5.0.0(@types/eslint@9.6.1)(eslint-config-prettier@9.0.0(eslint@8.0.0))(eslint@8.0.0)(prettier@3.0.0): + /eslint-plugin-prettier@5.0.0(eslint-config-prettier@9.0.0)(eslint@8.0.0)(prettier@3.0.0): + resolution: {integrity: sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '*' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true dependencies: eslint: 8.0.0 + eslint-config-prettier: 9.0.0(eslint@8.0.0) prettier: 3.0.0 prettier-linter-helpers: 1.0.0 synckit: 0.8.8 - optionalDependencies: - '@types/eslint': 9.6.1 - eslint-config-prettier: 9.0.0(eslint@8.0.0) + dev: true - eslint-scope@5.1.1: + /eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} dependencies: esrecurse: 4.3.0 estraverse: 4.3.0 + dev: true - eslint-scope@6.0.0: + /eslint-scope@6.0.0: + resolution: {integrity: sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 + dev: true - eslint-utils@3.0.0(eslint@8.0.0): + /eslint-utils@3.0.0(eslint@8.0.0): + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' dependencies: eslint: 8.0.0 eslint-visitor-keys: 2.1.0 + dev: true - eslint-visitor-keys@2.1.0: {} + /eslint-visitor-keys@2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + dev: true - eslint-visitor-keys@3.4.3: {} + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: true - eslint@8.0.0: + /eslint@8.0.0: + resolution: {integrity: sha512-03spzPzMAO4pElm44m60Nj08nYonPGQXmw6Ceai/S4QK82IgwWO1EXx1s9namKzVlbVu3Jf81hb+N+8+v21/HQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true dependencies: '@eslint/eslintrc': 1.4.1 '@humanwhocodes/config-array': 0.6.0 @@ -6105,34 +3818,64 @@ snapshots: v8-compile-cache: 2.4.0 transitivePeerDependencies: - supports-color + dev: true - espree@9.6.1: + /espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: acorn: 8.14.0 acorn-jsx: 5.3.2(acorn@8.14.0) eslint-visitor-keys: 3.4.3 + dev: true - esprima@4.0.1: {} + /esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + dev: true - esquery@1.6.0: + /esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} dependencies: estraverse: 5.3.0 + dev: true - esrecurse@4.3.0: + /esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} dependencies: estraverse: 5.3.0 + dev: true - estraverse@4.3.0: {} + /estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + dev: true - estraverse@5.3.0: {} + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: true - esutils@2.0.3: {} + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: true - etag@1.8.1: {} + /etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} - events@3.3.0: {} + /events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + dev: true - execa@4.1.0: + /execa@4.1.0: + resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} + engines: {node: '>=10'} dependencies: cross-spawn: 7.0.5 get-stream: 5.2.0 @@ -6143,8 +3886,11 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 strip-final-newline: 2.0.0 + dev: true - execa@5.1.1: + /execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} dependencies: cross-spawn: 7.0.5 get-stream: 6.0.1 @@ -6155,18 +3901,27 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 strip-final-newline: 2.0.0 + dev: true - exit@0.1.2: {} + /exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + dev: true - expect@29.7.0: + /expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/expect-utils': 29.7.0 jest-get-type: 29.6.3 jest-matcher-utils: 29.7.0 jest-message-util: 29.7.0 jest-util: 29.7.0 + dev: true - express@4.18.2: + /express@4.18.2: + resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} + engines: {node: '>= 0.10.0'} dependencies: accepts: 1.3.8 array-flatten: 1.1.1 @@ -6202,17 +3957,26 @@ snapshots: transitivePeerDependencies: - supports-color - external-editor@3.1.0: + /external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} dependencies: chardet: 0.7.0 iconv-lite: 0.4.24 tmp: 0.0.33 + dev: true - fast-deep-equal@3.1.3: {} + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: true - fast-diff@1.3.0: {} + /fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + dev: true - fast-glob@3.3.2: + /fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + engines: {node: '>=8.6.0'} dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 @@ -6220,37 +3984,58 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.8 - fast-json-stable-stringify@2.1.0: {} + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: true - fast-levenshtein@2.0.6: {} + /fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: true - fast-safe-stringify@2.1.1: {} + /fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fast-xml-parser@4.4.1: + /fast-xml-parser@4.4.1: + resolution: {integrity: sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==} + hasBin: true dependencies: strnum: 1.0.5 + dev: false - fastq@1.17.1: + /fastq@1.17.1: + resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} dependencies: reusify: 1.0.4 - fb-watchman@2.0.2: + /fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} dependencies: bser: 2.1.1 + dev: true - figures@3.2.0: + /figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} dependencies: escape-string-regexp: 1.0.5 + dev: true - file-entry-cache@6.0.1: + /file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} dependencies: flat-cache: 3.2.0 + dev: true - fill-range@7.1.1: + /fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 - finalhandler@1.2.0: + /finalhandler@1.2.0: + resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + engines: {node: '>= 0.8'} dependencies: debug: 2.6.9 encodeurl: 1.0.2 @@ -6262,29 +4047,56 @@ snapshots: transitivePeerDependencies: - supports-color - find-up@4.1.0: + /find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} dependencies: locate-path: 5.0.0 path-exists: 4.0.0 + dev: true - flat-cache@3.2.0: + /flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} dependencies: flatted: 3.3.1 keyv: 4.5.4 rimraf: 3.0.2 + dev: true - flat@5.0.2: {} + /flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + dev: false - flatted@3.3.1: {} + /flatted@3.3.1: + resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + dev: true - follow-redirects@1.15.9: {} + /follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false - foreground-child@3.3.0: + /foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} + engines: {node: '>=14'} dependencies: cross-spawn: 7.0.5 signal-exit: 4.1.0 + dev: false - fork-ts-checker-webpack-plugin@8.0.0(typescript@5.1.3)(webpack@5.87.0): + /fork-ts-checker-webpack-plugin@8.0.0(typescript@5.1.3)(webpack@5.87.0): + resolution: {integrity: sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==} + engines: {node: '>=12.13.0', yarn: '>=1.0.0'} + peerDependencies: + typescript: '>3.6.0' + webpack: ^5.11.0 dependencies: '@babel/code-frame': 7.26.2 chalk: 4.1.2 @@ -6300,49 +4112,82 @@ snapshots: tapable: 2.2.1 typescript: 5.1.3 webpack: 5.87.0 + dev: true - form-data@4.0.1: + /form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + engines: {node: '>= 6'} dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 - formidable@3.5.2: + /formidable@3.5.2: + resolution: {integrity: sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==} dependencies: dezalgo: 1.0.4 hexoid: 2.0.0 once: 1.4.0 + dev: true - forwarded@0.2.0: {} + /forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} - fresh@0.5.2: {} + /fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} - fs-extra@10.1.0: + /fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} dependencies: graceful-fs: 4.2.11 jsonfile: 6.1.0 universalify: 2.0.1 + dev: true - fs-monkey@1.0.6: {} + /fs-monkey@1.0.6: + resolution: {integrity: sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==} + dev: true - fs.realpath@1.0.0: {} + /fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + dev: true - fsevents@2.3.3: + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true optional: true - function-bind@1.1.2: {} + /function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - functional-red-black-tree@1.0.1: {} + /functional-red-black-tree@1.0.1: + resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} + dev: true - generate-function@2.3.1: + /generate-function@2.3.1: + resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} dependencies: is-property: 1.0.2 + dev: false - gensync@1.0.0-beta.2: {} + /gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + dev: true - get-caller-file@2.0.5: {} + /get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} - get-intrinsic@1.2.4: + /get-intrinsic@1.2.4: + resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + engines: {node: '>= 0.4'} dependencies: es-errors: 1.3.0 function-bind: 1.1.2 @@ -6350,25 +4195,43 @@ snapshots: has-symbols: 1.0.3 hasown: 2.0.2 - get-package-type@0.1.0: {} + /get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + dev: true - get-stream@5.2.0: + /get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} dependencies: pump: 3.0.2 + dev: true - get-stream@6.0.1: {} + /get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + dev: true - glob-parent@5.1.2: + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 - glob-parent@6.0.2: + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} dependencies: is-glob: 4.0.3 + dev: true - glob-to-regexp@0.4.1: {} + /glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + dev: true - glob@10.4.5: + /glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true dependencies: foreground-child: 3.3.0 jackspeak: 3.4.3 @@ -6376,8 +4239,11 @@ snapshots: minipass: 7.1.2 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 + dev: false - glob@7.2.3: + /glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported dependencies: fs.realpath: 1.0.0 inflight: 1.0.6 @@ -6385,21 +4251,33 @@ snapshots: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 + dev: true - glob@9.3.5: + /glob@9.3.5: + resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} + engines: {node: '>=16 || 14 >=14.17'} dependencies: fs.realpath: 1.0.0 minimatch: 8.0.4 minipass: 4.2.8 path-scurry: 1.11.1 + dev: true - globals@11.12.0: {} + /globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + dev: true - globals@13.24.0: + /globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} dependencies: type-fest: 0.20.2 + dev: true - globby@11.1.0: + /globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} dependencies: array-union: 2.1.0 dir-glob: 3.0.1 @@ -6407,38 +4285,65 @@ snapshots: ignore: 5.3.2 merge2: 1.4.1 slash: 3.0.0 + dev: true - gopd@1.0.1: + /gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: get-intrinsic: 1.2.4 - graceful-fs@4.2.11: {} + /graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + dev: true - graphemer@1.4.0: {} + /graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + dev: true - has-flag@4.0.0: {} + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} - has-own-prop@2.0.0: {} + /has-own-prop@2.0.0: + resolution: {integrity: sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==} + engines: {node: '>=8'} + dev: true - has-property-descriptors@1.0.2: + /has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} dependencies: es-define-property: 1.0.0 - has-proto@1.0.3: {} + /has-proto@1.0.3: + resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + engines: {node: '>= 0.4'} - has-symbols@1.0.3: {} + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} - hasown@2.0.2: + /hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} dependencies: function-bind: 1.1.2 - hexoid@2.0.0: {} + /hexoid@2.0.0: + resolution: {integrity: sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==} + engines: {node: '>=8'} + dev: true - highlight.js@10.7.3: {} + /highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + dev: false - html-escaper@2.0.2: {} + /html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + dev: true - http-errors@2.0.0: + /http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} dependencies: depd: 2.0.0 inherits: 2.0.4 @@ -6446,44 +4351,78 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 - human-signals@1.1.1: {} + /human-signals@1.1.1: + resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} + engines: {node: '>=8.12.0'} + dev: true - human-signals@2.1.0: {} + /human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + dev: true - iconv-lite@0.4.24: + /iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} dependencies: safer-buffer: 2.1.2 - iconv-lite@0.6.3: + /iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} dependencies: safer-buffer: 2.1.2 + dev: false - ieee754@1.2.1: {} + /ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore@4.0.6: {} + /ignore@4.0.6: + resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} + engines: {node: '>= 4'} + dev: true - ignore@5.3.2: {} + /ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + dev: true - import-fresh@3.3.0: + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 + dev: true - import-local@3.2.0: + /import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true dependencies: pkg-dir: 4.2.0 resolve-cwd: 3.0.0 + dev: true - imurmurhash@0.1.4: {} + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: true - inflight@1.0.6: + /inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. dependencies: once: 1.4.0 wrappy: 1.0.2 + dev: true - inherits@2.0.4: {} + /inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - inquirer@8.2.4: + /inquirer@8.2.4: + resolution: {integrity: sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==} + engines: {node: '>=12.0.0'} dependencies: ansi-escapes: 4.3.2 chalk: 4.1.2 @@ -6500,8 +4439,11 @@ snapshots: strip-ansi: 6.0.1 through: 2.3.8 wrap-ansi: 7.0.0 + dev: true - inquirer@8.2.5: + /inquirer@8.2.5: + resolution: {integrity: sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==} + engines: {node: '>=12.0.0'} dependencies: ansi-escapes: 4.3.2 chalk: 4.1.2 @@ -6518,48 +4460,91 @@ snapshots: strip-ansi: 6.0.1 through: 2.3.8 wrap-ansi: 7.0.0 + dev: true - interpret@1.4.0: {} + /interpret@1.4.0: + resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==} + engines: {node: '>= 0.10'} + dev: true - ipaddr.js@1.9.1: {} + /ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} - is-arrayish@0.2.1: {} + /is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + dev: true - is-binary-path@2.1.0: + /is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} dependencies: binary-extensions: 2.3.0 + dev: true - is-core-module@2.15.1: + /is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + engines: {node: '>= 0.4'} dependencies: hasown: 2.0.2 + dev: true - is-extglob@2.1.1: {} + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} - is-fullwidth-code-point@3.0.0: {} + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} - is-generator-fn@2.1.0: {} + /is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + dev: true - is-glob@4.0.3: + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 - is-interactive@1.0.0: {} + /is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + dev: true - is-number@7.0.0: {} + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} - is-property@1.0.2: {} + /is-property@1.0.2: + resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} + dev: false - is-stream@2.0.1: {} + /is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: true - is-unicode-supported@0.1.0: {} + /is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + dev: true - isarray@1.0.0: {} + /isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - isexe@2.0.0: {} + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - istanbul-lib-coverage@3.2.2: {} + /istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + dev: true - istanbul-lib-instrument@5.2.1: + /istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} dependencies: '@babel/core': 7.26.0 '@babel/parser': 7.26.2 @@ -6568,8 +4553,11 @@ snapshots: semver: 6.3.1 transitivePeerDependencies: - supports-color + dev: true - istanbul-lib-instrument@6.0.3: + /istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} dependencies: '@babel/core': 7.26.0 '@babel/parser': 7.26.2 @@ -6578,41 +4566,60 @@ snapshots: semver: 7.6.3 transitivePeerDependencies: - supports-color + dev: true - istanbul-lib-report@3.0.1: + /istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} dependencies: istanbul-lib-coverage: 3.2.2 make-dir: 4.0.0 supports-color: 7.2.0 + dev: true - istanbul-lib-source-maps@4.0.1: + /istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} dependencies: debug: 4.3.7 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: - supports-color + dev: true - istanbul-reports@3.1.7: + /istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} dependencies: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 + dev: true - iterare@1.2.1: {} + /iterare@1.2.1: + resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==} + engines: {node: '>=6'} - jackspeak@3.4.3: + /jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 + dev: false - jest-changed-files@29.7.0: + /jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: execa: 5.1.1 jest-util: 29.7.0 p-limit: 3.1.0 + dev: true - jest-circus@29.7.0: + /jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/environment': 29.7.0 '@jest/expect': 29.7.0 @@ -6637,17 +4644,26 @@ snapshots: transitivePeerDependencies: - babel-plugin-macros - supports-color + dev: true - jest-cli@29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): + /jest-cli@29.7.0(@types/node@20.3.1)(ts-node@10.9.1): + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true dependencies: - '@jest/core': 29.7.0(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) + '@jest/core': 29.7.0(ts-node@10.9.1) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) + create-jest: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) + jest-config: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -6656,12 +4672,24 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + dev: true - jest-config@29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): + /jest-config@29.7.0(@types/node@20.3.1)(ts-node@10.9.1): + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true dependencies: '@babel/core': 7.26.0 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 + '@types/node': 20.3.1 babel-jest: 29.7.0(@babel/core@7.26.0) chalk: 4.1.2 ci-info: 3.9.0 @@ -6681,33 +4709,43 @@ snapshots: pretty-format: 29.7.0 slash: 3.0.0 strip-json-comments: 3.1.1 - optionalDependencies: - '@types/node': 20.3.1 ts-node: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) transitivePeerDependencies: - babel-plugin-macros - supports-color + dev: true - jest-diff@29.7.0: + /jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: chalk: 4.1.2 diff-sequences: 29.6.3 jest-get-type: 29.6.3 pretty-format: 29.7.0 + dev: true - jest-docblock@29.7.0: + /jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: detect-newline: 3.1.0 + dev: true - jest-each@29.7.0: + /jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 jest-get-type: 29.6.3 jest-util: 29.7.0 pretty-format: 29.7.0 + dev: true - jest-environment-node@29.7.0: + /jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 @@ -6715,10 +4753,16 @@ snapshots: '@types/node': 20.3.1 jest-mock: 29.7.0 jest-util: 29.7.0 + dev: true - jest-get-type@29.6.3: {} + /jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true - jest-haste-map@29.7.0: + /jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 @@ -6733,20 +4777,29 @@ snapshots: walker: 1.0.8 optionalDependencies: fsevents: 2.3.3 + dev: true - jest-leak-detector@29.7.0: + /jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: jest-get-type: 29.6.3 pretty-format: 29.7.0 + dev: true - jest-matcher-utils@29.7.0: + /jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: chalk: 4.1.2 jest-diff: 29.7.0 jest-get-type: 29.6.3 pretty-format: 29.7.0 + dev: true - jest-message-util@29.7.0: + /jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/code-frame': 7.26.2 '@jest/types': 29.6.3 @@ -6757,27 +4810,47 @@ snapshots: pretty-format: 29.7.0 slash: 3.0.0 stack-utils: 2.0.6 + dev: true - jest-mock@29.7.0: + /jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 '@types/node': 20.3.1 jest-util: 29.7.0 + dev: true - jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): - optionalDependencies: + /jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + dependencies: jest-resolve: 29.7.0 + dev: true - jest-regex-util@29.6.3: {} + /jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true - jest-resolve-dependencies@29.7.0: + /jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: jest-regex-util: 29.6.3 jest-snapshot: 29.7.0 transitivePeerDependencies: - supports-color + dev: true - jest-resolve@29.7.0: + /jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: chalk: 4.1.2 graceful-fs: 4.2.11 @@ -6788,8 +4861,11 @@ snapshots: resolve: 1.22.8 resolve.exports: 2.0.2 slash: 3.0.0 + dev: true - jest-runner@29.7.0: + /jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/console': 29.7.0 '@jest/environment': 29.7.0 @@ -6814,8 +4890,11 @@ snapshots: source-map-support: 0.5.13 transitivePeerDependencies: - supports-color + dev: true - jest-runtime@29.7.0: + /jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 @@ -6841,8 +4920,11 @@ snapshots: strip-bom: 4.0.0 transitivePeerDependencies: - supports-color + dev: true - jest-snapshot@29.7.0: + /jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@babel/core': 7.26.0 '@babel/generator': 7.26.2 @@ -6866,8 +4948,11 @@ snapshots: semver: 7.6.3 transitivePeerDependencies: - supports-color + dev: true - jest-util@29.7.0: + /jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 '@types/node': 20.3.1 @@ -6875,8 +4960,11 @@ snapshots: ci-info: 3.9.0 graceful-fs: 4.2.11 picomatch: 2.3.1 + dev: true - jest-validate@29.7.0: + /jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.6.3 camelcase: 6.3.0 @@ -6884,8 +4972,11 @@ snapshots: jest-get-type: 29.6.3 leven: 3.1.0 pretty-format: 29.7.0 + dev: true - jest-watcher@29.7.0: + /jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 @@ -6895,76 +4986,128 @@ snapshots: emittery: 0.13.1 jest-util: 29.7.0 string-length: 4.0.2 + dev: true - jest-worker@27.5.1: + /jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} dependencies: '@types/node': 20.3.1 merge-stream: 2.0.0 supports-color: 8.1.1 + dev: true - jest-worker@29.7.0: + /jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@types/node': 20.3.1 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 + dev: true - jest@29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): + /jest@29.5.0(@types/node@20.3.1)(ts-node@10.9.1): + resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true dependencies: - '@jest/core': 29.7.0(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) + '@jest/core': 29.7.0(ts-node@10.9.1) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) + jest-cli: 29.7.0(@types/node@20.3.1)(ts-node@10.9.1) transitivePeerDependencies: - '@types/node' - babel-plugin-macros - supports-color - ts-node + dev: true - jiti@2.4.0: {} + /jiti@2.4.0: + resolution: {integrity: sha512-H5UpaUI+aHOqZXlYOaFP/8AzKsg+guWu+Pr3Y8i7+Y3zr1aXAvCvTAQ1RxSc6oVD8R8c7brgNtTVP91E7upH/g==} + hasBin: true + dev: false - joi@17.13.3: + /joi@17.13.3: + resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} dependencies: '@hapi/hoek': 9.3.0 '@hapi/topo': 5.1.0 '@sideway/address': 4.1.5 '@sideway/formula': 3.0.1 '@sideway/pinpoint': 2.0.0 + dev: false - js-tokens@4.0.0: {} + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: true - js-yaml@3.14.1: + /js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true dependencies: argparse: 1.0.10 esprima: 4.0.1 + dev: true - js-yaml@4.1.0: + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true dependencies: argparse: 2.0.1 - jsesc@3.0.2: {} + /jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + dev: true - json-buffer@3.0.1: {} + /json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + dev: true - json-parse-even-better-errors@2.3.1: {} + /json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + dev: true - json-schema-traverse@0.4.1: {} + /json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: true - json-schema-traverse@1.0.0: {} + /json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + dev: true - json-stable-stringify-without-jsonify@1.0.1: {} + /json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: true - json5@2.2.3: {} + /json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + dev: true - jsonc-parser@3.2.0: {} + /jsonc-parser@3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: true - jsonfile@6.1.0: + /jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} dependencies: universalify: 2.0.1 optionalDependencies: graceful-fs: 4.2.11 + dev: true - jsonwebtoken@9.0.2: + /jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} dependencies: jws: 3.2.2 lodash.includes: 4.3.0 @@ -6976,42 +5119,68 @@ snapshots: lodash.once: 4.1.1 ms: 2.1.3 semver: 7.6.3 + dev: false - jwa@1.4.1: + /jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} dependencies: buffer-equal-constant-time: 1.0.1 ecdsa-sig-formatter: 1.0.11 safe-buffer: 5.2.1 + dev: false - jws@3.2.2: + /jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} dependencies: jwa: 1.4.1 safe-buffer: 5.2.1 + dev: false - keyv@4.5.4: + /keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} dependencies: json-buffer: 3.0.1 + dev: true - kleur@3.0.3: {} + /kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + dev: true - leven@3.1.0: {} + /leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + dev: true - levn@0.4.1: + /levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 + dev: true - libphonenumber-js@1.11.14: {} + /libphonenumber-js@1.11.14: + resolution: {integrity: sha512-sexvAfwcW1Lqws4zFp8heAtAEXbEDnvkYCEGzvOoMgZR7JhXo/IkE9MkkGACgBed5fWqh3ShBGnJBdDnU9N8EQ==} - lines-and-columns@1.2.4: {} + /lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: true - loader-runner@4.3.0: {} + /loader-runner@4.3.0: + resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} + engines: {node: '>=6.11.5'} + dev: true - locate-path@5.0.0: + /locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} dependencies: p-locate: 4.1.0 + dev: true - locter@2.1.5: + /locter@2.1.5: + resolution: {integrity: sha512-eI57PuVxigQ0GBscGIIFGPB467E5zKODHD3XGuknzLvf7HdnvRw3GdZVGj1J8XKsKOYovZQesX/oOdTwbdjwuQ==} dependencies: destr: 2.0.3 ebec: 2.3.0 @@ -7019,124 +5188,223 @@ snapshots: flat: 5.0.2 jiti: 2.4.0 yaml: 2.6.0 + dev: false - lodash.includes@4.3.0: {} + /lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + dev: false - lodash.isboolean@3.0.3: {} + /lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + dev: false - lodash.isinteger@4.0.4: {} + /lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + dev: false - lodash.isnumber@3.0.3: {} + /lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + dev: false - lodash.isplainobject@4.0.6: {} + /lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + dev: false - lodash.isstring@4.0.1: {} + /lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + dev: false - lodash.memoize@4.1.2: {} + /lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + dev: true - lodash.merge@4.6.2: {} + /lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: true - lodash.once@4.1.1: {} + /lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + dev: false - lodash@4.17.21: {} + /lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - log-symbols@4.1.0: + /log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} dependencies: chalk: 4.1.2 is-unicode-supported: 0.1.0 + dev: true - long@5.2.3: {} + /long@5.2.3: + resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} + dev: false - lower-case@2.0.2: + /lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} dependencies: tslib: 2.8.1 + dev: false - lru-cache@10.4.3: {} + /lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@5.1.1: + /lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: yallist: 3.1.1 + dev: true - lru-cache@7.18.3: {} + /lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + dev: false - lru.min@1.1.1: {} + /lru.min@1.1.1: + resolution: {integrity: sha512-FbAj6lXil6t8z4z3j0E5mfRlPzxkySotzUHwRXjlpRh10vc6AI6WN62ehZj82VG7M20rqogJ0GLwar2Xa05a8Q==} + engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} + dev: false - macos-release@2.5.1: {} + /macos-release@2.5.1: + resolution: {integrity: sha512-DXqXhEM7gW59OjZO8NIjBCz9AQ1BEMrfiOAl4AYByHCtVHRF4KoGNO8mqQeM8lRCtQe/UnJ4imO/d2HdkKsd+A==} + engines: {node: '>=6'} + dev: true - magic-string@0.30.0: + /magic-string@0.30.0: + resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==} + engines: {node: '>=12'} dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + dev: true - make-dir@4.0.0: + /make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} dependencies: semver: 7.6.3 + dev: true - make-error@1.3.6: {} + /make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - makeerror@1.0.12: + /makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} dependencies: tmpl: 1.0.5 + dev: true - media-typer@0.3.0: {} + /media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} - memfs@3.5.3: + /memfs@3.5.3: + resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} + engines: {node: '>= 4.0.0'} dependencies: fs-monkey: 1.0.6 + dev: true - merge-descriptors@1.0.1: {} + /merge-descriptors@1.0.1: + resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} - merge-stream@2.0.0: {} + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: true - merge2@1.4.1: {} + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} - methods@1.1.2: {} + /methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} - micromatch@4.0.8: + /micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} dependencies: braces: 3.0.3 picomatch: 2.3.1 - mime-db@1.52.0: {} + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} - mime-types@2.1.35: + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} dependencies: mime-db: 1.52.0 - mime@1.6.0: {} + /mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true - mime@2.6.0: {} + /mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + dev: true - mimic-fn@2.1.0: {} + /mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + dev: true - minimatch@3.1.2: + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.11 + dev: true - minimatch@8.0.4: + /minimatch@8.0.4: + resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==} + engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 + dev: true - minimatch@9.0.5: + /minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 - minimist@1.2.8: {} + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@4.2.8: {} + /minipass@4.2.8: + resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} + engines: {node: '>=8'} + dev: true - minipass@7.1.2: {} + /minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} - mkdirp@0.5.6: + /mkdirp@0.5.6: + resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} + hasBin: true dependencies: minimist: 1.2.8 - mkdirp@2.1.6: {} + /mkdirp@2.1.6: + resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} + engines: {node: '>=10'} + hasBin: true + dev: false - ms@2.0.0: {} + /ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - ms@2.1.3: {} + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - multer@1.4.4-lts.1: + /multer@1.4.4-lts.1: + resolution: {integrity: sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==} + engines: {node: '>= 6.0.0'} dependencies: append-field: 1.0.0 busboy: 1.6.0 @@ -7146,9 +5414,13 @@ snapshots: type-is: 1.6.18 xtend: 4.0.2 - mute-stream@0.0.8: {} + /mute-stream@0.0.8: + resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} + dev: true - mysql2@3.11.4: + /mysql2@3.11.4: + resolution: {integrity: sha512-Z2o3tY4Z8EvSRDwknaC40MdZ3+m0sKbpnXrShQLdxPrAvcNli7jLrD2Zd2IzsRMw4eK9Yle500FDmlkIqp+krg==} + engines: {node: '>= 8.0'} dependencies: aws-ssl-profiles: 1.1.2 denque: 2.1.0 @@ -7159,69 +5431,123 @@ snapshots: named-placeholders: 1.1.3 seq-queue: 0.0.5 sqlstring: 2.3.3 + dev: false - mz@2.7.0: + /mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} dependencies: any-promise: 1.3.0 object-assign: 4.1.1 thenify-all: 1.6.0 + dev: false - named-placeholders@1.1.3: + /named-placeholders@1.1.3: + resolution: {integrity: sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==} + engines: {node: '>=12.0.0'} dependencies: lru-cache: 7.18.3 + dev: false - natural-compare@1.4.0: {} + /natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: true - negotiator@0.6.3: {} + /negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} - neo-async@2.6.2: {} + /neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + dev: true - no-case@3.0.4: + /no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} dependencies: lower-case: 2.0.2 tslib: 2.8.1 + dev: false - node-abort-controller@3.1.1: {} + /node-abort-controller@3.1.1: + resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + dev: true - node-addon-api@8.2.2: {} + /node-addon-api@8.2.2: + resolution: {integrity: sha512-9emqXAKhVoNrQ792nLI/wpzPpJ/bj/YXxW0CvAau1+RdGBcCRF1Dmz7719zgVsQNrzHl9Tzn3ImZ4qWFarWL0A==} + engines: {node: ^18 || ^20 || >= 21} + dev: false - node-emoji@1.11.0: + /node-emoji@1.11.0: + resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} dependencies: lodash: 4.17.21 + dev: true - node-fetch@2.7.0: + /node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true dependencies: whatwg-url: 5.0.0 - node-gyp-build@4.8.2: {} + /node-gyp-build@4.8.2: + resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==} + hasBin: true + dev: false - node-int64@0.4.0: {} + /node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + dev: true - node-releases@2.0.18: {} + /node-releases@2.0.18: + resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} + dev: true - normalize-path@3.0.0: {} + /normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true - npm-run-path@4.0.1: + /npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} dependencies: path-key: 3.1.1 + dev: true - object-assign@4.1.1: {} + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} - object-inspect@1.13.3: {} + /object-inspect@1.13.3: + resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==} + engines: {node: '>= 0.4'} - on-finished@2.4.1: + /on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} dependencies: ee-first: 1.1.1 - once@1.4.0: + /once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: wrappy: 1.0.2 + dev: true - onetime@5.1.2: + /onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} dependencies: mimic-fn: 2.1.0 + dev: true - optionator@0.9.4: + /optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} dependencies: deep-is: 0.1.4 fast-levenshtein: 2.0.6 @@ -7229,8 +5555,11 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 word-wrap: 1.2.5 + dev: true - ora@5.4.1: + /ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} dependencies: bl: 4.1.0 chalk: 4.1.2 @@ -7241,99 +5570,179 @@ snapshots: log-symbols: 4.1.0 strip-ansi: 6.0.1 wcwidth: 1.0.1 + dev: true - os-name@4.0.1: + /os-name@4.0.1: + resolution: {integrity: sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw==} + engines: {node: '>=10'} dependencies: macos-release: 2.5.1 windows-release: 4.0.0 + dev: true - os-tmpdir@1.0.2: {} + /os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + dev: true - p-limit@2.3.0: + /p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} dependencies: p-try: 2.2.0 + dev: true - p-limit@3.1.0: + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} dependencies: yocto-queue: 0.1.0 + dev: true - p-locate@4.1.0: + /p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} dependencies: p-limit: 2.3.0 + dev: true - p-try@2.2.0: {} + /p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + dev: true - package-json-from-dist@1.0.1: {} + /package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + dev: false - parent-module@1.0.1: + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} dependencies: callsites: 3.1.0 + dev: true - parse-json@5.2.0: + /parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} dependencies: '@babel/code-frame': 7.26.2 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + dev: true - parse5-htmlparser2-tree-adapter@6.0.1: + /parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} dependencies: parse5: 6.0.1 + dev: false - parse5@5.1.1: {} + /parse5@5.1.1: + resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} + dev: false - parse5@6.0.1: {} + /parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + dev: false - parseurl@1.3.3: {} + /parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} - pascal-case@3.1.2: + /pascal-case@3.1.2: + resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} dependencies: no-case: 3.0.4 tslib: 2.8.1 + dev: false - path-exists@4.0.0: {} + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: true - path-is-absolute@1.0.1: {} + /path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + dev: true - path-key@3.1.1: {} + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} - path-parse@1.0.7: {} + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true - path-scurry@1.11.1: + /path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} dependencies: lru-cache: 10.4.3 minipass: 7.1.2 - path-to-regexp@0.1.7: {} + /path-to-regexp@0.1.7: + resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} - path-to-regexp@3.2.0: {} + /path-to-regexp@3.2.0: + resolution: {integrity: sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==} - path-to-regexp@3.3.0: {} + /path-to-regexp@3.3.0: + resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} + dev: false - path-type@4.0.0: {} + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: true - pg-cloudflare@1.1.1: + /pg-cloudflare@1.1.1: + resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} + requiresBuild: true + dev: false optional: true - pg-connection-string@2.7.0: {} + /pg-connection-string@2.7.0: + resolution: {integrity: sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==} + dev: false - pg-int8@1.0.1: {} + /pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + dev: false - pg-pool@3.7.0(pg@8.13.1): + /pg-pool@3.7.0(pg@8.13.1): + resolution: {integrity: sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==} + peerDependencies: + pg: '>=8.0' dependencies: pg: 8.13.1 + dev: false - pg-protocol@1.7.0: {} + /pg-protocol@1.7.0: + resolution: {integrity: sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==} + dev: false - pg-types@2.2.0: + /pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} dependencies: pg-int8: 1.0.1 postgres-array: 2.0.0 postgres-bytea: 1.0.0 postgres-date: 1.0.7 postgres-interval: 1.2.0 + dev: false - pg@8.13.1: + /pg@8.13.1: + resolution: {integrity: sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true dependencies: pg-connection-string: 2.7.0 pg-pool: 3.7.0(pg@8.13.1) @@ -7342,110 +5751,188 @@ snapshots: pgpass: 1.0.5 optionalDependencies: pg-cloudflare: 1.1.1 + dev: false - pgpass@1.0.5: + /pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} dependencies: split2: 4.2.0 + dev: false - picocolors@1.1.1: {} + /picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + dev: true - picomatch@2.3.1: {} + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} - pirates@4.0.6: {} + /pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + dev: true - pkg-dir@4.2.0: + /pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} dependencies: find-up: 4.1.0 + dev: true - pluralize@8.0.0: {} + /pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + dev: true - postgres-array@2.0.0: {} + /postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + dev: false - postgres-bytea@1.0.0: {} + /postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + dev: false - postgres-date@1.0.7: {} + /postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + dev: false - postgres-interval@1.2.0: + /postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} dependencies: xtend: 4.0.2 + dev: false - prelude-ls@1.2.1: {} + /prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: true - prettier-linter-helpers@1.0.0: + /prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} dependencies: fast-diff: 1.3.0 + dev: true - prettier@3.0.0: {} + /prettier@3.0.0: + resolution: {integrity: sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==} + engines: {node: '>=14'} + hasBin: true + dev: true - pretty-format@29.7.0: + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 react-is: 18.3.1 + dev: true - process-nextick-args@2.0.1: {} + /process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - progress@2.0.3: {} + /progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + dev: true - prompts@2.4.2: + /prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} dependencies: kleur: 3.0.3 sisteransi: 1.0.5 + dev: true - proxy-addr@2.0.7: + /proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} dependencies: forwarded: 0.2.0 ipaddr.js: 1.9.1 - proxy-from-env@1.1.0: {} + /proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + dev: false - pump@3.0.2: + /pump@3.0.2: + resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} dependencies: end-of-stream: 1.4.4 once: 1.4.0 + dev: true - punycode@2.3.1: {} + /punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + dev: true - pure-rand@6.1.0: {} + /pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + dev: true - qs@6.11.0: + /qs@6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} dependencies: side-channel: 1.0.6 - qs@6.13.0: + /qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} dependencies: side-channel: 1.0.6 + dev: true - queue-microtask@1.2.3: {} + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - randombytes@2.1.0: + /randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} dependencies: safe-buffer: 5.2.1 + dev: true - range-parser@1.2.1: {} + /range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} - rapiq@0.9.0: + /rapiq@0.9.0: + resolution: {integrity: sha512-k4oT4RarFBrlLMJ49xUTeQpa/us0uU4I70D/UEnK3FWQ4GENzei01rEQAmvPKAIzACo4NMW+YcYJ7EVfSa7EFg==} dependencies: ebec: 1.1.1 smob: 1.5.0 + dev: false - raw-body@2.5.1: + /raw-body@2.5.1: + resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + engines: {node: '>= 0.8'} dependencies: bytes: 3.1.2 http-errors: 2.0.0 iconv-lite: 0.4.24 unpipe: 1.0.0 - raw-body@2.5.2: + /raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} dependencies: bytes: 3.1.2 http-errors: 2.0.0 iconv-lite: 0.4.24 unpipe: 1.0.0 - react-is@18.3.1: {} + /react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + dev: true - readable-stream@2.3.8: + /readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} dependencies: core-util-is: 1.0.3 inherits: 2.0.4 @@ -7455,90 +5942,161 @@ snapshots: string_decoder: 1.1.1 util-deprecate: 1.0.2 - readable-stream@3.6.2: + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} dependencies: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 + dev: true - readdirp@3.6.0: + /readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 + dev: true - rechoir@0.6.2: + /rechoir@0.6.2: + resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} + engines: {node: '>= 0.10'} dependencies: resolve: 1.22.8 + dev: true - reflect-metadata@0.2.0: {} + /reflect-metadata@0.2.0: + resolution: {integrity: sha512-vUN0wuk3MuhSVMfU/ImnPQAK8QZcXJ339DtVsP3jDscxCe6dT+PsOe3J1BYS9Ec2Fd4oC6ry6bCBebzTya0IYw==} + deprecated: This version has a critical bug in fallback handling. Please upgrade to reflect-metadata@0.2.2 or newer. - reflect-metadata@0.2.2: {} + /reflect-metadata@0.2.2: + resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==} + dev: false - regexpp@3.2.0: {} + /regexpp@3.2.0: + resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} + engines: {node: '>=8'} + dev: true - repeat-string@1.6.1: {} + /repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + dev: true - require-directory@2.1.1: {} + /require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} - require-from-string@2.0.2: {} + /require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + dev: true - resolve-cwd@3.0.0: + /resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} dependencies: resolve-from: 5.0.0 + dev: true - resolve-from@4.0.0: {} + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: true - resolve-from@5.0.0: {} + /resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + dev: true - resolve.exports@2.0.2: {} + /resolve.exports@2.0.2: + resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} + engines: {node: '>=10'} + dev: true - resolve@1.22.8: + /resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true dependencies: is-core-module: 2.15.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + dev: true - restore-cursor@3.1.0: + /restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} dependencies: onetime: 5.1.2 signal-exit: 3.0.7 + dev: true - reusify@1.0.4: {} + /reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@3.0.2: + /rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true dependencies: glob: 7.2.3 + dev: true - rimraf@4.4.1: + /rimraf@4.4.1: + resolution: {integrity: sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==} + engines: {node: '>=14'} + hasBin: true dependencies: glob: 9.3.5 + dev: true - run-async@2.4.1: {} + /run-async@2.4.1: + resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} + engines: {node: '>=0.12.0'} + dev: true - run-parallel@1.2.0: + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 - rxjs@7.8.1: + /rxjs@7.8.1: + resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: tslib: 2.8.1 - safe-buffer@5.1.2: {} + /safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - safe-buffer@5.2.1: {} + /safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safer-buffer@2.1.2: {} + /safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - schema-utils@3.3.0: + /schema-utils@3.3.0: + resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} + engines: {node: '>= 10.13.0'} dependencies: '@types/json-schema': 7.0.15 ajv: 6.12.6 ajv-keywords: 3.5.2(ajv@6.12.6) + dev: true - semver@6.3.1: {} + /semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + dev: true - semver@7.6.3: {} + /semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} + engines: {node: '>=10'} + hasBin: true - send@0.18.0: + /send@0.18.0: + resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + engines: {node: '>= 0.8.0'} dependencies: debug: 2.6.9 depd: 2.0.0 @@ -7556,13 +6114,19 @@ snapshots: transitivePeerDependencies: - supports-color - seq-queue@0.0.5: {} + /seq-queue@0.0.5: + resolution: {integrity: sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==} + dev: false - serialize-javascript@6.0.2: + /serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} dependencies: randombytes: 2.1.0 + dev: true - serve-static@1.15.0: + /serve-static@1.15.0: + resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} + engines: {node: '>= 0.8.0'} dependencies: encodeurl: 1.0.2 escape-html: 1.0.3 @@ -7571,7 +6135,9 @@ snapshots: transitivePeerDependencies: - supports-color - set-function-length@1.2.2: + /set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} dependencies: define-data-property: 1.1.4 es-errors: 1.3.0 @@ -7580,116 +6146,201 @@ snapshots: gopd: 1.0.1 has-property-descriptors: 1.0.2 - setprototypeof@1.2.0: {} + /setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - sha.js@2.4.11: + /sha.js@2.4.11: + resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} + hasBin: true dependencies: inherits: 2.0.4 safe-buffer: 5.2.1 + dev: false - shebang-command@2.0.0: + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 - shebang-regex@3.0.0: {} + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} - shelljs@0.8.5: + /shelljs@0.8.5: + resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} + engines: {node: '>=4'} + hasBin: true dependencies: glob: 7.2.3 interpret: 1.4.0 rechoir: 0.6.2 + dev: true - side-channel@1.0.6: + /side-channel@1.0.6: + resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + engines: {node: '>= 0.4'} dependencies: call-bind: 1.0.7 es-errors: 1.3.0 get-intrinsic: 1.2.4 object-inspect: 1.13.3 - signal-exit@3.0.7: {} + /signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: true - signal-exit@4.1.0: {} + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: false - sisteransi@1.0.5: {} + /sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + dev: true - slash@3.0.0: {} + /slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + dev: true - smob@1.5.0: {} + /smob@1.5.0: + resolution: {integrity: sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==} + dev: false - source-map-support@0.5.13: + /source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} dependencies: buffer-from: 1.1.2 source-map: 0.6.1 + dev: true - source-map-support@0.5.21: + /source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} dependencies: buffer-from: 1.1.2 source-map: 0.6.1 + dev: true - source-map@0.6.1: {} + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: true - source-map@0.7.4: {} + /source-map@0.7.4: + resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} + engines: {node: '>= 8'} + dev: true - split2@4.2.0: {} + /split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + dev: false - sprintf-js@1.0.3: {} + /sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + dev: true - sqlstring@2.3.3: {} + /sqlstring@2.3.3: + resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} + engines: {node: '>= 0.6'} + dev: false - stack-utils@2.0.6: + /stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} dependencies: escape-string-regexp: 2.0.0 + dev: true - statuses@2.0.1: {} + /statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} - std-env@3.8.0: {} + /std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} + dev: false - streamsearch@1.1.0: {} + /streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} - string-length@4.0.2: + /string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} dependencies: char-regex: 1.0.2 strip-ansi: 6.0.1 + dev: true - string-width@4.2.3: + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string-width@5.1.2: + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 + dev: false - string_decoder@1.1.1: + /string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} dependencies: safe-buffer: 5.1.2 - string_decoder@1.3.0: + /string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: safe-buffer: 5.2.1 + dev: true - strip-ansi@6.0.1: + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.0: + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} dependencies: ansi-regex: 6.1.0 + dev: false - strip-bom@3.0.0: {} + /strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + dev: true - strip-bom@4.0.0: {} + /strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + dev: true - strip-final-newline@2.0.0: {} + /strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + dev: true - strip-json-comments@3.1.1: {} + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: true - strnum@1.0.5: {} + /strnum@1.0.5: + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + dev: false - superagent@9.0.2: + /superagent@9.0.2: + resolution: {integrity: sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==} + engines: {node: '>=14.18.0'} dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 @@ -7702,38 +6353,75 @@ snapshots: qs: 6.13.0 transitivePeerDependencies: - supports-color + dev: true - supertest@7.0.0: + /supertest@7.0.0: + resolution: {integrity: sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==} + engines: {node: '>=14.18.0'} dependencies: methods: 1.1.2 superagent: 9.0.2 transitivePeerDependencies: - supports-color + dev: true - supports-color@7.2.0: + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} dependencies: has-flag: 4.0.0 - supports-color@8.1.1: + /supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} dependencies: has-flag: 4.0.0 + dev: true - supports-preserve-symlinks-flag@1.0.0: {} + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true - swagger-ui-dist@5.18.2: + /swagger-ui-dist@5.18.2: + resolution: {integrity: sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw==} dependencies: '@scarf/scarf': 1.4.0 + dev: false - symbol-observable@4.0.0: {} + /symbol-observable@4.0.0: + resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} + engines: {node: '>=0.10'} + dev: true - synckit@0.8.8: + /synckit@0.8.8: + resolution: {integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==} + engines: {node: ^14.18.0 || >=16.0.0} dependencies: '@pkgr/core': 0.1.1 tslib: 2.8.1 + dev: true - tapable@2.2.1: {} + /tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + dev: true - terser-webpack-plugin@5.3.10(webpack@5.87.0): + /terser-webpack-plugin@5.3.10(webpack@5.87.0): + resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 @@ -7741,57 +6429,136 @@ snapshots: serialize-javascript: 6.0.2 terser: 5.36.0 webpack: 5.87.0 + dev: true - terser@5.36.0: + /terser-webpack-plugin@5.3.10(webpack@5.96.1): + resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + dependencies: + '@jridgewell/trace-mapping': 0.3.25 + jest-worker: 27.5.1 + schema-utils: 3.3.0 + serialize-javascript: 6.0.2 + terser: 5.36.0 + webpack: 5.96.1 + dev: true + + /terser@5.36.0: + resolution: {integrity: sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==} + engines: {node: '>=10'} + hasBin: true dependencies: '@jridgewell/source-map': 0.3.6 acorn: 8.14.0 commander: 2.20.3 source-map-support: 0.5.21 + dev: true - test-exclude@6.0.0: + /test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} dependencies: '@istanbuljs/schema': 0.1.3 glob: 7.2.3 minimatch: 3.1.2 + dev: true - text-table@0.2.0: {} + /text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + dev: true - thenify-all@1.6.0: + /thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} dependencies: thenify: 3.3.1 + dev: false - thenify@3.3.1: + /thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} dependencies: any-promise: 1.3.0 + dev: false - through@2.3.8: {} + /through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + dev: true - tmp@0.0.33: + /tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} dependencies: os-tmpdir: 1.0.2 + dev: true - tmpl@1.0.5: {} + /tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + dev: true - to-regex-range@5.0.1: + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 - toidentifier@1.0.1: {} + /toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} - tr46@0.0.3: {} + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - tree-kill@1.2.2: {} + /tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + dev: true - ts-api-utils@1.4.0(typescript@5.1.3): + /ts-api-utils@1.4.0(typescript@5.1.3): + resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' dependencies: typescript: 5.1.3 + dev: true - ts-jest@29.1.0(@babel/core@7.26.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)))(typescript@5.1.3): + /ts-jest@29.1.0(@babel/core@7.26.0)(jest@29.5.0)(typescript@5.1.3): + resolution: {integrity: sha512-ZhNr7Z4PcYa+JjMl62ir+zPiNJfXJN6E8hSLnaUKhOgqcn8vb3e537cpkd0FuAfRK3sR1LSqM1MOhliXNgOFPA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/types': ^29.0.0 + babel-jest: ^29.0.0 + esbuild: '*' + jest: ^29.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true dependencies: + '@babel/core': 7.26.0 bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.5.0(@types/node@20.3.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) + jest: 29.5.0(@types/node@20.3.1)(ts-node@10.9.1) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -7799,21 +6566,36 @@ snapshots: semver: 7.6.3 typescript: 5.1.3 yargs-parser: 21.1.1 - optionalDependencies: - '@babel/core': 7.26.0 - '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.0) + dev: true - ts-loader@9.4.3(typescript@5.1.3)(webpack@5.87.0): + /ts-loader@9.4.3(typescript@5.1.3)(webpack@5.96.1): + resolution: {integrity: sha512-n3hBnm6ozJYzwiwt5YRiJZkzktftRpMiBApHaJPoWLA+qetQBAXkHqCLM6nwSdRDimqVtA5ocIkcTRLMTt7yzA==} + engines: {node: '>=12.0.0'} + peerDependencies: + typescript: '*' + webpack: ^5.0.0 dependencies: chalk: 4.1.2 enhanced-resolve: 5.17.1 micromatch: 4.0.8 semver: 7.6.3 typescript: 5.1.3 - webpack: 5.87.0 + webpack: 5.96.1 + dev: true - ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3): + /ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3): + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 @@ -7831,40 +6613,68 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - tsconfig-paths-webpack-plugin@4.0.1: + /tsconfig-paths-webpack-plugin@4.0.1: + resolution: {integrity: sha512-m5//KzLoKmqu2MVix+dgLKq70MnFi8YL8sdzQZ6DblmCdfuq/y3OqvJd5vMndg2KEVCOeNz8Es4WVZhYInteLw==} + engines: {node: '>=10.13.0'} dependencies: chalk: 4.1.2 enhanced-resolve: 5.17.1 tsconfig-paths: 4.2.0 + dev: true - tsconfig-paths@4.2.0: + /tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} dependencies: json5: 2.2.3 minimist: 1.2.8 strip-bom: 3.0.0 + dev: true - tslib@2.5.3: {} + /tslib@2.5.3: + resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==} - tslib@2.8.1: {} + /tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - type-check@0.4.0: + /type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} dependencies: prelude-ls: 1.2.1 + dev: true - type-detect@4.0.8: {} + /type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + dev: true - type-fest@0.20.2: {} + /type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + dev: true - type-fest@0.21.3: {} + /type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + dev: true - type-is@1.6.18: + /type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} dependencies: media-typer: 0.3.0 mime-types: 2.1.35 - typedarray@0.0.6: {} + /typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typeorm-extension@3.6.3(typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3))): + /typeorm-extension@3.6.3(typeorm@0.3.20): + resolution: {integrity: sha512-AE+8KqBphlBdVz5JS77o6LZzzi+b+YFFt8So4Qu/KRo/iynAwekrx98Oxuu3FAYNm6DUKDcubOBMZsJeiRvHkA==} + engines: {node: '>=14.0.0'} + hasBin: true + peerDependencies: + typeorm: ~0.3.0 dependencies: '@faker-js/faker': 8.4.1 consola: 3.2.3 @@ -7874,10 +6684,67 @@ snapshots: rapiq: 0.9.0 reflect-metadata: 0.2.2 smob: 1.5.0 - typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)) + typeorm: 0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1) yargs: 17.7.2 + dev: false - typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1(@types/node@20.3.1)(typescript@5.1.3)): + /typeorm@0.3.20(mysql2@3.11.4)(pg@8.13.1)(ts-node@10.9.1): + resolution: {integrity: sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q==} + engines: {node: '>=16.13.0'} + hasBin: true + peerDependencies: + '@google-cloud/spanner': ^5.18.0 + '@sap/hana-client': ^2.12.25 + better-sqlite3: ^7.1.2 || ^8.0.0 || ^9.0.0 + hdb-pool: ^0.1.6 + ioredis: ^5.0.4 + mongodb: ^5.8.0 + mssql: ^9.1.1 || ^10.0.1 + mysql2: ^2.2.5 || ^3.0.1 + oracledb: ^6.3.0 + pg: ^8.5.1 + pg-native: ^3.0.0 + pg-query-stream: ^4.0.0 + redis: ^3.1.1 || ^4.0.0 + sql.js: ^1.4.0 + sqlite3: ^5.0.3 + ts-node: ^10.7.0 + typeorm-aurora-data-api-driver: ^2.0.0 + peerDependenciesMeta: + '@google-cloud/spanner': + optional: true + '@sap/hana-client': + optional: true + better-sqlite3: + optional: true + hdb-pool: + optional: true + ioredis: + optional: true + mongodb: + optional: true + mssql: + optional: true + mysql2: + optional: true + oracledb: + optional: true + pg: + optional: true + pg-native: + optional: true + pg-query-stream: + optional: true + redis: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + ts-node: + optional: true + typeorm-aurora-data-api-driver: + optional: true dependencies: '@sqltools/formatter': 1.2.5 app-root-path: 3.1.0 @@ -7889,78 +6756,133 @@ snapshots: dotenv: 16.4.5 glob: 10.4.5 mkdirp: 2.1.6 + mysql2: 3.11.4 + pg: 8.13.1 reflect-metadata: 0.2.2 sha.js: 2.4.11 + ts-node: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) tslib: 2.8.1 uuid: 9.0.1 yargs: 17.7.2 - optionalDependencies: - mysql2: 3.11.4 - pg: 8.13.1 - ts-node: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) transitivePeerDependencies: - supports-color + dev: false - typescript@5.1.3: {} + /typescript@5.1.3: + resolution: {integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==} + engines: {node: '>=14.17'} + hasBin: true - uid@2.0.2: + /uid@2.0.2: + resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} + engines: {node: '>=8'} dependencies: '@lukeed/csprng': 1.1.0 - universalify@2.0.1: {} + /universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + dev: true - unpipe@1.0.0: {} + /unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} - update-browserslist-db@1.1.1(browserslist@4.24.2): + /update-browserslist-db@1.1.1(browserslist@4.24.2): + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' dependencies: browserslist: 4.24.2 escalade: 3.2.0 picocolors: 1.1.1 + dev: true - uri-js@4.4.1: + /uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: punycode: 2.3.1 + dev: true - util-deprecate@1.0.2: {} + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - utils-merge@1.0.1: {} + /utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} - uuid@9.0.1: {} + /uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + dev: false - v8-compile-cache-lib@3.0.1: {} + /v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - v8-compile-cache@2.4.0: {} + /v8-compile-cache@2.4.0: + resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==} + dev: true - v8-to-istanbul@9.3.0: + /v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} dependencies: '@jridgewell/trace-mapping': 0.3.25 '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 + dev: true - validator@13.12.0: {} + /validator@13.12.0: + resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} + engines: {node: '>= 0.10'} - vary@1.1.2: {} + /vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} - walker@1.0.8: + /walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} dependencies: makeerror: 1.0.12 + dev: true - watchpack@2.4.2: + /watchpack@2.4.2: + resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==} + engines: {node: '>=10.13.0'} dependencies: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 + dev: true - wcwidth@1.0.1: + /wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} dependencies: defaults: 1.0.4 + dev: true - webidl-conversions@3.0.1: {} + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - webpack-node-externals@3.0.0: {} + /webpack-node-externals@3.0.0: + resolution: {integrity: sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==} + engines: {node: '>=6'} + dev: true - webpack-sources@3.2.3: {} + /webpack-sources@3.2.3: + resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} + engines: {node: '>=10.13.0'} + dev: true - webpack@5.87.0: + /webpack@5.87.0: + resolution: {integrity: sha512-GOu1tNbQ7p1bDEoFRs2YPcfyGs8xq52yyPBZ3m2VGnXGtV9MxjrkABHm4V9Ia280OefsSLzvbVoXcfLxjKY/Iw==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.6 @@ -7990,56 +6912,136 @@ snapshots: - '@swc/core' - esbuild - uglify-js + dev: true + + /webpack@5.96.1: + resolution: {integrity: sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.6 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.14.0 + browserslist: 4.24.2 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.17.1 + es-module-lexer: 1.5.4 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.0 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 3.3.0 + tapable: 2.2.1 + terser-webpack-plugin: 5.3.10(webpack@5.96.1) + watchpack: 2.4.2 + webpack-sources: 3.2.3 + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + dev: true - whatwg-url@5.0.0: + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 - which@2.0.2: + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true dependencies: isexe: 2.0.0 - windows-release@4.0.0: + /windows-release@4.0.0: + resolution: {integrity: sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg==} + engines: {node: '>=10'} dependencies: execa: 4.1.0 + dev: true - word-wrap@1.2.5: {} + /word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + dev: true - wrap-ansi@7.0.0: + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - wrap-ansi@8.1.0: + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} dependencies: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 + dev: false - wrappy@1.0.2: {} + /wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + dev: true - write-file-atomic@4.0.2: + /write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} dependencies: imurmurhash: 0.1.4 signal-exit: 3.0.7 + dev: true - xtend@4.0.2: {} + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} - y18n@5.0.8: {} + /y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} - yallist@3.1.1: {} + /yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + dev: true - yaml@1.10.2: {} + /yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + dev: true - yaml@2.6.0: {} + /yaml@2.6.0: + resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==} + engines: {node: '>= 14'} + hasBin: true + dev: false - yargs-parser@20.2.9: {} + /yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + dev: false - yargs-parser@21.1.1: {} + /yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} - yargs@16.2.0: + /yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} dependencies: cliui: 7.0.4 escalade: 3.2.0 @@ -8048,8 +7050,11 @@ snapshots: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 20.2.9 + dev: false - yargs@17.7.2: + /yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} dependencies: cliui: 8.0.1 escalade: 3.2.0 @@ -8059,6 +7064,11 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yn@3.1.1: {} + /yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} - yocto-queue@0.1.0: {} + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: true diff --git a/src/course/course.entity.ts b/src/course/course.entity.ts index 792234a..a4765d5 100644 --- a/src/course/course.entity.ts +++ b/src/course/course.entity.ts @@ -36,8 +36,6 @@ export class Course { @ManyToOne(() => User) @JoinColumn({ name: 'teacher_id' }) teacher: User; - @Column({ name: 'teacher_id' }) - teacherId: string; @ManyToOne(() => Category) @JoinColumn({ name: 'category_id' }) diff --git a/src/pretest/pretest.service.ts b/src/pretest/pretest.service.ts index d2218df..0f579f9 100644 --- a/src/pretest/pretest.service.ts +++ b/src/pretest/pretest.service.ts @@ -1,10 +1,18 @@ +import { HttpService } from '@nestjs/axios'; import { BadRequestException, Inject, Injectable, NotFoundException, } from '@nestjs/common'; -import { Pretest } from './pretest.entity'; +import { ConfigService } from '@nestjs/config'; +import { QuestionOptionService } from 'src/question-option/question-option.service'; +import { QuestionService } from 'src/question/question.service'; +import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; +import { QuestionType, Role } from 'src/shared/enums'; +import { createPagination } from 'src/shared/pagination'; +import { UserBackgroundService } from 'src/user-background/user-background.service'; +import { User } from 'src/user/user.entity'; import { FindOneOptions, FindOptionsSelect, @@ -12,20 +20,11 @@ import { ILike, Repository, } from 'typeorm'; -import { PaginatedPretestResponseDto } from './dtos/pretest-response.dto'; -import { createPagination } from 'src/shared/pagination'; -import { User } from 'src/user/user.entity'; -import { QuestionType, Role } from 'src/shared/enums'; import { CreatePretestDto } from './dtos/create-pretest.dto'; -import { UpdatePretestDto } from './dtos/update-pretest.dto'; -import { UserService } from 'src/user/user.service'; -import { UserBackgroundService } from 'src/user-background/user-background.service'; -import { HttpService } from '@nestjs/axios'; -import { ConfigService } from '@nestjs/config'; +import { PaginatedPretestResponseDto } from './dtos/pretest-response.dto'; import { PretestDto } from './dtos/pretest.dto'; -import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; -import { QuestionService } from 'src/question/question.service'; -import { QuestionOptionService } from 'src/question-option/question-option.service'; +import { UpdatePretestDto } from './dtos/update-pretest.dto'; +import { Pretest } from './pretest.entity'; @Injectable() export class PretestService { @@ -248,6 +247,7 @@ export class PretestService { ); return { data: response.data }; } catch (error) { + console.log(error); throw new Error('Failed to fetch data or process request'); } } diff --git a/src/roadmap/dtos/create-roadmp-ai.dto.ts b/src/roadmap/dtos/create-roadmp-ai.dto.ts new file mode 100644 index 0000000..90b773c --- /dev/null +++ b/src/roadmap/dtos/create-roadmp-ai.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class CreateRoadmapAiDto { + @IsString() + @IsNotEmpty() + @ApiProperty({ + description: 'Roadmap AI Pretest Description', + type: String, + example: 'AI Roadmap', + }) + preTestDescription: string; +} diff --git a/src/roadmap/roadmap.controller.ts b/src/roadmap/roadmap.controller.ts index c9e3fd7..fdde46c 100644 --- a/src/roadmap/roadmap.controller.ts +++ b/src/roadmap/roadmap.controller.ts @@ -20,6 +20,7 @@ import { Role } from 'src/shared/enums'; import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { CreateRoadmapDto, RoadmapResponseDto } from './dtos'; import { RoadmapService } from './roadmap.service'; +import { CreateRoadmapAiDto } from './dtos/create-roadmp-ai.dto'; @Controller('roadmap') @Injectable() @@ -90,7 +91,7 @@ export class RoadmapController { @HttpCode(HttpStatus.CREATED) async create( @Req() request: AuthenticatedRequest, - @Body() createRoadmapDto: CreateRoadmapDto, + @Body() createRoadmapDto: CreateRoadmapAiDto, ) { return await this.roadmapService.create(request.user.id, createRoadmapDto); } diff --git a/src/roadmap/roadmap.module.ts b/src/roadmap/roadmap.module.ts index 8ac6a0c..4674dcf 100644 --- a/src/roadmap/roadmap.module.ts +++ b/src/roadmap/roadmap.module.ts @@ -1,11 +1,22 @@ +import { HttpModule } from '@nestjs/axios'; import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; import { TypeOrmModule } from '@nestjs/typeorm'; +import { CourseModule } from 'src/course-module/course-module.entity'; +import { UserBackgroundModule } from 'src/user-background/user-background.module'; import { RoadmapController } from './roadmap.controller'; import { Roadmap } from './roadmap.entity'; import { RoadmapService } from './roadmap.service'; +import { Course } from 'src/course/course.entity'; @Module({ - imports: [TypeOrmModule.forFeature([Roadmap])], + imports: [ + TypeOrmModule.forFeature([Roadmap, Course]), + HttpModule, + ConfigModule, + UserBackgroundModule, + CourseModule, + ], controllers: [RoadmapController], providers: [RoadmapService], }) diff --git a/src/roadmap/roadmap.service.ts b/src/roadmap/roadmap.service.ts index 4cfc91d..7ef3799 100644 --- a/src/roadmap/roadmap.service.ts +++ b/src/roadmap/roadmap.service.ts @@ -1,35 +1,109 @@ +import { HttpService } from '@nestjs/axios'; import { Inject, Injectable, InternalServerErrorException, NotFoundException, } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { Course } from 'src/course/course.entity'; +import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; import { createPagination } from 'src/shared/pagination'; +import { UserBackgroundService } from 'src/user-background/user-background.service'; import { FindOneOptions, FindOptionsWhere, Repository } from 'typeorm'; -import { - CreateRoadmapDto, - PaginatedRoadmapResponseDto, - UpdateRoadmapDto, -} from './dtos'; +import { PaginatedRoadmapResponseDto, UpdateRoadmapDto } from './dtos'; +import { CreateRoadmapAiDto } from './dtos/create-roadmp-ai.dto'; import { Roadmap } from './roadmap.entity'; - @Injectable() export class RoadmapService { constructor( @Inject('RoadmapRepository') private readonly roadmapRepository: Repository, + @Inject('CourseRepository') + private readonly courseRepository: Repository, + private readonly httpService: HttpService, + private readonly configService: ConfigService, + private readonly userBackgroundService: UserBackgroundService, ) {} - async create( + private readonly defaultRelations = { + teacher: true, + category: true, + }; + + async fetchRoadmapData( userId: string, - createRoadmapDto: CreateRoadmapDto, - ): Promise { + cerateRoadmapAiDto: CreateRoadmapAiDto, + ) { + const userBackground = await this.userBackgroundService.findOneByUserId( + userId, + ); + const course = await this.courseRepository.find({ + relations: this.defaultRelations, + }); try { - return await this.roadmapRepository.save({ - ...createRoadmapDto, - user: { id: userId }, - courses: createRoadmapDto.courses.map((courseId) => ({ id: courseId })), - }); + const requestBody = { + courses: course, + user_data: { + age: '39', + department: 'Computer Science', + interest: userBackground.topics.map((topic) => topic.title), + name: userBackground.user.fullname, + preTestDescription: cerateRoadmapAiDto.preTestDescription, + preTestScore: 70, + university: 'Yale', + userID: userId, + }, + }; + console.log('Request body', requestBody); + const response = await this.httpService.axiosRef.post( + `https://ai.edusaig.com/ai/generate-roadmap`, + requestBody, + ); + return { data: response.data }; + } catch (error) { + throw new InternalServerErrorException(error); + } + } + + async create(userId: string, cerateRoadmapAiDto: CreateRoadmapAiDto) { + try { + const roadmap = await this.fetchRoadmapData(userId, cerateRoadmapAiDto); + + await Promise.all( + roadmap.data.validated_roadmap.recommended_courses.map( + async (course) => { + const courseData = await this.courseRepository.findOne({ + where: { id: course.id }, + }); + + if (courseData) { + const existingRoadmap = await this.roadmapRepository.findOne({ + where: { + user: { id: userId }, + courses: { id: course.id }, + }, + }); + + if (!existingRoadmap) { + const requestBody = { + duration: course.duration.toString(), + priority: course.priority, + courses: [course.id], + }; + + await this.roadmapRepository.save({ + ...requestBody, + user: { id: userId }, + courses: requestBody.courses.map((courseId) => ({ + id: courseId, + })), + }); + } + } + }, + ), + ); } catch (error) { if (error instanceof NotFoundException) throw error; throw new InternalServerErrorException(error.message); diff --git a/src/user-background/user-background.controller.ts b/src/user-background/user-background.controller.ts index 9f96f48..9a15790 100644 --- a/src/user-background/user-background.controller.ts +++ b/src/user-background/user-background.controller.ts @@ -87,7 +87,7 @@ export class UserBackgroundController { type: UserBackgroundResponseDto, description: 'Create a user background', }) - @Roles(Role.ADMIN) + @Roles(Role.STUDENT) async create( @Body() data: CreateUserBackground, ): Promise { From 2cee851507e82349d676a208f942987cd1eecda8 Mon Sep 17 00:00:00 2001 From: khris-xp Date: Thu, 28 Nov 2024 20:38:00 +0700 Subject: [PATCH 142/155] fix: create roadmap ai dto typo --- src/roadmap/roadmap.service.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/roadmap/roadmap.service.ts b/src/roadmap/roadmap.service.ts index 7ef3799..88e17b8 100644 --- a/src/roadmap/roadmap.service.ts +++ b/src/roadmap/roadmap.service.ts @@ -33,7 +33,7 @@ export class RoadmapService { async fetchRoadmapData( userId: string, - cerateRoadmapAiDto: CreateRoadmapAiDto, + createRoadmapAiDto: CreateRoadmapAiDto, ) { const userBackground = await this.userBackgroundService.findOneByUserId( userId, @@ -49,7 +49,7 @@ export class RoadmapService { department: 'Computer Science', interest: userBackground.topics.map((topic) => topic.title), name: userBackground.user.fullname, - preTestDescription: cerateRoadmapAiDto.preTestDescription, + preTestDescription: createRoadmapAiDto.preTestDescription, preTestScore: 70, university: 'Yale', userID: userId, @@ -66,9 +66,9 @@ export class RoadmapService { } } - async create(userId: string, cerateRoadmapAiDto: CreateRoadmapAiDto) { + async create(userId: string, createRoadmapAiDto: CreateRoadmapAiDto) { try { - const roadmap = await this.fetchRoadmapData(userId, cerateRoadmapAiDto); + const roadmap = await this.fetchRoadmapData(userId, createRoadmapAiDto); await Promise.all( roadmap.data.validated_roadmap.recommended_courses.map( From 1e6d09087e1487b45c29771a56f41403f2cc7362 Mon Sep 17 00:00:00 2001 From: khris-xp Date: Thu, 28 Nov 2024 21:32:51 +0700 Subject: [PATCH 143/155] feat: connect with ai evaluate --- src/pretest/dtos/evaluate.dto.ts | 14 +++++++ src/pretest/dtos/pretest.dto.ts | 2 +- src/pretest/interfaces/create-evaluate.d.ts | 5 +++ src/pretest/pretest.controller.ts | 36 +++++++++++++---- src/pretest/pretest.module.ts | 2 + src/pretest/pretest.service.ts | 43 +++++++++++++++++++++ 6 files changed, 94 insertions(+), 8 deletions(-) create mode 100644 src/pretest/dtos/evaluate.dto.ts create mode 100644 src/pretest/interfaces/create-evaluate.d.ts diff --git a/src/pretest/dtos/evaluate.dto.ts b/src/pretest/dtos/evaluate.dto.ts new file mode 100644 index 0000000..95f68da --- /dev/null +++ b/src/pretest/dtos/evaluate.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class EvaluateResponseDto { + @ApiProperty({ + description: 'The result of the evaluation', + type: String, + example: 'Passed', + }) + result: string; + + constructor(result: string) { + this.result = result; + } +} diff --git a/src/pretest/dtos/pretest.dto.ts b/src/pretest/dtos/pretest.dto.ts index 169fd49..265e8f0 100644 --- a/src/pretest/dtos/pretest.dto.ts +++ b/src/pretest/dtos/pretest.dto.ts @@ -76,4 +76,4 @@ export class PretestDto { ], }) data: QuestionAiDto[]; -} +} \ No newline at end of file diff --git a/src/pretest/interfaces/create-evaluate.d.ts b/src/pretest/interfaces/create-evaluate.d.ts new file mode 100644 index 0000000..04bb241 --- /dev/null +++ b/src/pretest/interfaces/create-evaluate.d.ts @@ -0,0 +1,5 @@ +export interface CreateEvaluate { + question: string[]; + correct_answer: string[]; + user_answer: string[]; +} diff --git a/src/pretest/pretest.controller.ts b/src/pretest/pretest.controller.ts index b520da7..1aca4d0 100644 --- a/src/pretest/pretest.controller.ts +++ b/src/pretest/pretest.controller.ts @@ -14,18 +14,18 @@ import { Req, } from '@nestjs/common'; import { ApiBearerAuth, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger'; -import { PretestService } from './pretest.service'; -import { - PaginatedPretestResponseDto, - PretestResponseDto, -} from './dtos/pretest-response.dto'; import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; -import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { Roles } from 'src/shared/decorators/role.decorator'; import { Role } from 'src/shared/enums'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { CreatePretestDto } from './dtos/create-pretest.dto'; +import { EvaluateResponseDto } from './dtos/evaluate.dto'; +import { + PaginatedPretestResponseDto, + PretestResponseDto, +} from './dtos/pretest-response.dto'; import { UpdatePretestDto } from './dtos/update-pretest.dto'; - +import { PretestService } from './pretest.service'; @Controller('pretest') @ApiTags('Pretest') @ApiBearerAuth() @@ -99,6 +99,28 @@ export class PretestController { return new PretestResponseDto(pretest); } + @Post('/evaluate/:id') + @Roles(Role.STUDENT) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Evaluate a pretest', + type: EvaluateResponseDto, + }) + async evaluatePretest( + @Req() request: AuthenticatedRequest, + @Param( + 'id', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + id: string, + ) { + const pretest = await this.pretestService.evaluatePretest(request.user, id); + return new EvaluateResponseDto(pretest); + } + @Post() @Roles(Role.STUDENT) @ApiResponse({ diff --git a/src/pretest/pretest.module.ts b/src/pretest/pretest.module.ts index 70f6251..cdd581c 100644 --- a/src/pretest/pretest.module.ts +++ b/src/pretest/pretest.module.ts @@ -12,6 +12,7 @@ import { HttpModule } from '@nestjs/axios'; import { ConfigModule } from '@nestjs/config'; import { QuestionModule } from 'src/question/question.module'; import { QuestionOptionModule } from 'src/question-option/question-option.module'; +import { ExamAnswerModule } from 'src/exam-answer/exam-answer.module'; @Module({ imports: [ @@ -22,6 +23,7 @@ import { QuestionOptionModule } from 'src/question-option/question-option.module ConfigModule, QuestionModule, QuestionOptionModule, + ExamAnswerModule, ], controllers: [PretestController], providers: [...pretestProviders, PretestService], diff --git a/src/pretest/pretest.service.ts b/src/pretest/pretest.service.ts index 0f579f9..2f587ee 100644 --- a/src/pretest/pretest.service.ts +++ b/src/pretest/pretest.service.ts @@ -6,6 +6,8 @@ import { NotFoundException, } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; +import { JwtPayloadDto } from 'src/auth/dtos/jwt-payload.dto'; +import { ExamAnswerService } from 'src/exam-answer/exam-answer.service'; import { QuestionOptionService } from 'src/question-option/question-option.service'; import { QuestionService } from 'src/question/question.service'; import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; @@ -24,6 +26,7 @@ import { CreatePretestDto } from './dtos/create-pretest.dto'; import { PaginatedPretestResponseDto } from './dtos/pretest-response.dto'; import { PretestDto } from './dtos/pretest.dto'; import { UpdatePretestDto } from './dtos/update-pretest.dto'; +import { CreateEvaluate } from './interfaces/create-evaluate'; import { Pretest } from './pretest.entity'; @Injectable() @@ -38,6 +41,7 @@ export class PretestService { private readonly configService: ConfigService, private readonly questionService: QuestionService, private readonly questionOptionService: QuestionOptionService, + private readonly examAnswerService: ExamAnswerService, ) {} async findAll( userId: string, @@ -202,6 +206,14 @@ export class PretestService { }; } + async fetchEvaluation(requestBody: CreateEvaluate) { + const response = await this.httpService.axiosRef.post( + `https://ai.edusaig.com/ai/evaluate`, + requestBody, + ); + return { data: response.data }; + } + async fetchData(userId: string, pretestId: string): Promise { const userBackground = await this.userBackgroundService.findOneByUserId( userId, @@ -252,6 +264,37 @@ export class PretestService { } } + async evaluatePretest( + user: JwtPayloadDto, + pretestId: string, + ): Promise { + const preTestExam = + await this.examAnswerService.findExamAnswerPretestByPretestId( + user.id, + user.role, + pretestId, + { + page: 1, + limit: 10, + search: '', + }, + ); + + const requestBody: CreateEvaluate = { + question: preTestExam.data.map((data) => data.question.question), + correct_answer: preTestExam.data.map( + (data) => data.correctAnswer.optionText, + ), + user_answer: preTestExam.data.map( + (data) => data.selectedOption.optionText, + ), + }; + + const response = await this.fetchEvaluation(requestBody); + + return response.data; + } + async createQuestionAndChoice( pretestId: string, userId: string, From d625b804a8e966974cd6c589158b2e851e3ec167 Mon Sep 17 00:00:00 2001 From: Rachchanon Klaisuban Date: Fri, 29 Nov 2024 10:29:23 +0700 Subject: [PATCH 144/155] feat: search exam by couseModuleID --- src/exam/exam.controller.ts | 49 +++++++++++++++++++++++++++++++++++++ src/exam/exam.service.ts | 47 +++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) diff --git a/src/exam/exam.controller.ts b/src/exam/exam.controller.ts index 94ada0f..64f2d29 100644 --- a/src/exam/exam.controller.ts +++ b/src/exam/exam.controller.ts @@ -100,6 +100,55 @@ export class ExamController { return new ExamResponseDto(exam); } + @Get('course-module/:courseModuleId') + @ApiResponse({ + status: HttpStatus.OK, + description: 'Returns all exams by courseModule id', + type: PaginatedExamResponseDto, + isArray: true, + }) + @ApiQuery({ + name: 'page', + type: Number, + required: false, + description: 'Page number', + }) + @ApiQuery({ + name: 'limit', + type: Number, + required: false, + description: 'Items per page', + }) + @ApiQuery({ + name: 'search', + type: String, + required: false, + description: 'Search by title', + }) + async findExamByCourseModuleId( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + @Param( + 'courseModuleId', + new ParseUUIDPipe({ + version: '4', + errorHttpStatusCode: HttpStatus.UNPROCESSABLE_ENTITY, + }), + ) + courseModuleId: string, + ): Promise { + return await this.examService.findExamByCourseModuleId( + request.user.id, + request.user.role, + courseModuleId, + { + page: query.page, + limit: query.limit, + search: query.search, + }, + ); + } + @Post() @Roles(Role.TEACHER) @ApiResponse({ diff --git a/src/exam/exam.service.ts b/src/exam/exam.service.ts index 59ecc3e..f0dc13c 100644 --- a/src/exam/exam.service.ts +++ b/src/exam/exam.service.ts @@ -165,6 +165,53 @@ export class ExamService { return exam; } + async findExamByCourseModuleId( + userId: string, + role: Role, + courseModuleId: string, + { + page = 1, + limit = 20, + search = '', + }: { + page?: number; + limit?: number; + search?: string; + }, + ): Promise { + const { find } = await createPagination(this.examRepository, { + page, + limit, + }); + + const whereCondition = this.validateAndCreateCondition( + userId, + role, + search, + ); + whereCondition['courseModule'] = { id: courseModuleId }; + + const courseModule = await this.courseModuleRepository.findOne({ + where: { id: courseModuleId }, + }); + + if (!courseModule) { + throw new NotFoundException('courseModule not found.'); + } + + return await find({ + where: whereCondition, + relations: [ + 'courseModule', + 'courseModule.course', + 'courseModule.course.teacher', + ], + select: { + courseModule: this.selectPopulateCourseModule(), + }, + }).run(); + } + async createExam(createExamDto: CreateExamDto): Promise { const courseModule = await this.courseModuleRepository.findOne({ where: { id: createExamDto.courseModuleId }, From a3f7e2a005375574653934927d14fa27761a0e78 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Fri, 29 Nov 2024 11:26:38 +0700 Subject: [PATCH 145/155] feat: enhance user background and occupation controllers with public access and improve service integration --- .../user-background-topic.controller.ts | 4 ++-- .../user-background-topic.module.ts | 1 + src/user-background/user-background.controller.ts | 6 ++++-- src/user-background/user-background.entity.ts | 10 +--------- src/user-background/user-background.service.ts | 13 ++++++++++++- src/user-occupation/user-occupation.controller.ts | 2 ++ 6 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/user-background-topic/user-background-topic.controller.ts b/src/user-background-topic/user-background-topic.controller.ts index d27bfbf..8338ea9 100644 --- a/src/user-background-topic/user-background-topic.controller.ts +++ b/src/user-background-topic/user-background-topic.controller.ts @@ -10,7 +10,6 @@ import { Patch, Post, Query, - Req, } from '@nestjs/common'; import { ApiBearerAuth, @@ -19,7 +18,6 @@ import { ApiResponse, ApiTags, } from '@nestjs/swagger'; -import { AuthenticatedRequest } from 'src/auth/interfaces/authenticated-request.interface'; import { Roles } from 'src/shared/decorators/role.decorator'; import { Role } from 'src/shared/enums'; import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; @@ -30,6 +28,7 @@ import { UserBackgroundTopicResponseDto, } from './dtos/user-background-response.dto'; import { UserBackgroundTopicService } from './user-background-topic.service'; +import { Public } from 'src/shared/decorators/public.decorator'; @Controller('user-background-topic') @ApiTags('User Background Topic') @@ -84,6 +83,7 @@ export class UserBackgroundTopicController { @Post() @Roles(Role.ADMIN) + @Public() @ApiResponse({ status: HttpStatus.CREATED, type: UserBackgroundTopicResponseDto, diff --git a/src/user-background-topic/user-background-topic.module.ts b/src/user-background-topic/user-background-topic.module.ts index 392ca31..0647926 100644 --- a/src/user-background-topic/user-background-topic.module.ts +++ b/src/user-background-topic/user-background-topic.module.ts @@ -8,5 +8,6 @@ import { UserBackgroundTopicService } from './user-background-topic.service'; imports: [TypeOrmModule.forFeature([UserBackgroundTopic])], controllers: [UserBackgroundTopicController], providers: [UserBackgroundTopicService], + exports: [UserBackgroundTopicService] }) export class UserBackgroundTopicModule {} diff --git a/src/user-background/user-background.controller.ts b/src/user-background/user-background.controller.ts index 9a15790..b36c643 100644 --- a/src/user-background/user-background.controller.ts +++ b/src/user-background/user-background.controller.ts @@ -35,7 +35,9 @@ import { UserBackgroundService } from './user-background.service'; @ApiBearerAuth() @Injectable() export class UserBackgroundController { - constructor(private readonly userBackgroundService: UserBackgroundService) {} + constructor( + private readonly userBackgroundService: UserBackgroundService, + ) {} @Get() @ApiResponse({ @@ -87,7 +89,7 @@ export class UserBackgroundController { type: UserBackgroundResponseDto, description: 'Create a user background', }) - @Roles(Role.STUDENT) + @Roles(Role.STUDENT) async create( @Body() data: CreateUserBackground, ): Promise { diff --git a/src/user-background/user-background.entity.ts b/src/user-background/user-background.entity.ts index 344254d..aa91505 100644 --- a/src/user-background/user-background.entity.ts +++ b/src/user-background/user-background.entity.ts @@ -27,15 +27,7 @@ export class UserBackground { @ManyToMany(() => UserBackgroundTopic, (topic) => topic.userBackgrounds) @JoinTable({ - name: 'user_background_topics_mapping', - joinColumn: { - name: 'user_background_id', - referencedColumnName: 'id', - }, - inverseJoinColumn: { - name: 'topic_id', - referencedColumnName: 'id', - }, + name: 'user_background_topics_mapping' }) topics: UserBackgroundTopic[]; diff --git a/src/user-background/user-background.service.ts b/src/user-background/user-background.service.ts index 335e3f3..89fe4e8 100644 --- a/src/user-background/user-background.service.ts +++ b/src/user-background/user-background.service.ts @@ -5,7 +5,7 @@ import { } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { createPagination } from 'src/shared/pagination'; -import { UserBackgroundTopic } from 'src/user-background-topic/user-background-topic.entity'; +import { UserBackgroundTopicService } from 'src/user-background-topic/user-background-topic.service'; import { DeepPartial, FindOneOptions, @@ -25,6 +25,7 @@ export class UserBackgroundService { constructor( @InjectRepository(UserBackground) private readonly userBackgroundRepository: Repository, + private readonly userBackgroundTopicService: UserBackgroundTopicService, ) {} async findAll({ @@ -74,9 +75,19 @@ export class UserBackgroundService { } async create(data: CreateUserBackground): Promise { + const topics = await Promise.all( + data.topics.map(async (topic) => { + return await this.userBackgroundTopicService.findOne(topic, { + where: { + id: topic, + }, + }); + }), + ); const userBackground = this.userBackgroundRepository.create({ userId: data.userId, occupationId: data.occupationId, + topics, }); const savedUserBackground = await this.userBackgroundRepository.save( diff --git a/src/user-occupation/user-occupation.controller.ts b/src/user-occupation/user-occupation.controller.ts index 80381a5..b4b332d 100644 --- a/src/user-occupation/user-occupation.controller.ts +++ b/src/user-occupation/user-occupation.controller.ts @@ -29,6 +29,7 @@ import { UserOccupationResponseDto, } from './dtos/user-occupation-response.dto'; import { UserOccupationService } from './user-occupation.service'; +import { Public } from 'src/shared/decorators/public.decorator'; @Controller('user-occupation') @ApiTags('User Occupation') @@ -39,6 +40,7 @@ export class UserOccupationController { @Post() @Roles(Role.ADMIN) + @Public() @ApiResponse({ status: HttpStatus.CREATED, type: UserOccupationResponseDto, From c598290174e6c1dc9606edadb727739b5978701d Mon Sep 17 00:00:00 2001 From: khris-xp Date: Fri, 29 Nov 2024 15:17:12 +0700 Subject: [PATCH 146/155] feat: create progress and enroll by user endpoint --- src/enrollment/enrollment.controller.ts | 15 +++++++++++++ src/enrollment/enrollment.service.ts | 28 +++++++++++++++++++++++++ src/pretest/pretest.service.ts | 2 -- src/progress/progress.controller.ts | 15 +++++++++++++ src/progress/progress.service.ts | 24 +++++++++++++++++++++ src/roadmap/roadmap.service.ts | 5 +++-- 6 files changed, 85 insertions(+), 4 deletions(-) diff --git a/src/enrollment/enrollment.controller.ts b/src/enrollment/enrollment.controller.ts index 276d2ad..6ba16a2 100644 --- a/src/enrollment/enrollment.controller.ts +++ b/src/enrollment/enrollment.controller.ts @@ -47,6 +47,21 @@ export class EnrollmentController { return this.enrollmentService.findAll(query); } + @Get('/user') + @ApiResponse({ + status: HttpStatus.OK, + type: EnrollmentResponseDto, + description: 'Get all enrollments by user', + isArray: true, + }) + @Roles(Role.STUDENT) + async findAllByUser( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return await this.enrollmentService.findAllByUser(request.user.id, query); + } + @Get(':id') @ApiParam({ name: 'id', diff --git a/src/enrollment/enrollment.service.ts b/src/enrollment/enrollment.service.ts index 815c94d..afc4613 100644 --- a/src/enrollment/enrollment.service.ts +++ b/src/enrollment/enrollment.service.ts @@ -58,6 +58,34 @@ export class EnrollmentService { return enrollment; } + async findAllByUser( + userId: string, + { + page = 1, + limit = 20, + }: { + page?: number; + limit?: number; + }, + ): Promise { + const { find } = await createPagination(this.enrollmentRepository, { + page, + limit, + }); + + const enrollments = await find({ + where: { + user: { id: userId }, + }, + relations: { + user: true, + course: true, + }, + }).run(); + + return enrollments; + } + async create(createEnrollmentDto: CreateEnrollmentDto): Promise { const enrollment = await this.enrollmentRepository.findOne({ where: { diff --git a/src/pretest/pretest.service.ts b/src/pretest/pretest.service.ts index 2f587ee..24c8682 100644 --- a/src/pretest/pretest.service.ts +++ b/src/pretest/pretest.service.ts @@ -259,7 +259,6 @@ export class PretestService { ); return { data: response.data }; } catch (error) { - console.log(error); throw new Error('Failed to fetch data or process request'); } } @@ -279,7 +278,6 @@ export class PretestService { search: '', }, ); - const requestBody: CreateEvaluate = { question: preTestExam.data.map((data) => data.question.question), correct_answer: preTestExam.data.map( diff --git a/src/progress/progress.controller.ts b/src/progress/progress.controller.ts index 7a5e3a1..4e5f35c 100644 --- a/src/progress/progress.controller.ts +++ b/src/progress/progress.controller.ts @@ -69,6 +69,21 @@ export class ProgressController { return this.progressService.findAll(query); } + @Get('/user') + @ApiResponse({ + status: HttpStatus.OK, + type: ProgressResponseDto, + description: 'Get all progress by user', + isArray: true, + }) + @Roles(Role.STUDENT) + async findAllByUser( + @Req() request: AuthenticatedRequest, + @Query() query: PaginateQueryDto, + ): Promise { + return this.progressService.findAllByUser(request.user.id, query); + } + @Get(':id') @ApiParam({ name: 'id', diff --git a/src/progress/progress.service.ts b/src/progress/progress.service.ts index 3a586f7..a1fba3a 100644 --- a/src/progress/progress.service.ts +++ b/src/progress/progress.service.ts @@ -1,6 +1,7 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { createPagination } from 'src/shared/pagination'; +import { PaginateQueryDto } from 'src/shared/pagination/dtos/paginate-query.dto'; import { FindOneOptions, FindOptionsWhere, Repository } from 'typeorm'; import { CreateProgressDto } from './dtos/create-progress.dto'; import { PaginatedProgressResponseDto } from './dtos/progress-response.dto'; @@ -36,6 +37,29 @@ export class ProgressService { return progress; } + async findAllByUser( + userId: string, + query: PaginateQueryDto, + ): Promise { + const { find } = await createPagination(this.progressRepository, query); + + const progress = await find({ + where: { + enrollment: { + user: { + id: userId, + }, + }, + }, + relations: { + enrollment: true, + chapter: true, + }, + }).run(); + + return progress; + } + async findOne( id: string, options: FindOneOptions, diff --git a/src/roadmap/roadmap.service.ts b/src/roadmap/roadmap.service.ts index 88e17b8..49fa5c7 100644 --- a/src/roadmap/roadmap.service.ts +++ b/src/roadmap/roadmap.service.ts @@ -55,7 +55,6 @@ export class RoadmapService { userID: userId, }, }; - console.log('Request body', requestBody); const response = await this.httpService.axiosRef.post( `https://ai.edusaig.com/ai/generate-roadmap`, requestBody, @@ -129,7 +128,9 @@ export class RoadmapService { const queryBuilder = this.roadmapRepository .createQueryBuilder('roadmap') .leftJoinAndSelect('roadmap.user', 'user') - .leftJoinAndSelect('roadmap.courses', 'courses'); + .leftJoinAndSelect('roadmap.courses', 'courses') + .leftJoinAndSelect('courses.teacher', 'teacher') + .leftJoinAndSelect('courses.category', 'category'); if (userId) { queryBuilder.andWhere('user.id = :userId', { userId }); From 2791f9967a5725cbdf49e1dec6d6f9a424a2e82e Mon Sep 17 00:00:00 2001 From: ganthepro Date: Fri, 29 Nov 2024 15:21:38 +0700 Subject: [PATCH 147/155] chore: remove unnecessary switch to root step in GitHub Actions workflow --- .github/workflows/main.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ac48a07..303f127 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,9 +13,6 @@ jobs: build-and-deploy: runs-on: self-hosted steps: - - name: switch to root - run: sudo -s - - name: Copy Repository uses: actions/checkout@v4 From 7ed7e69ea1038669f149448d2dbd12d0e7ff859b Mon Sep 17 00:00:00 2001 From: khris-xp Date: Fri, 29 Nov 2024 15:30:49 +0700 Subject: [PATCH 148/155] feat: change find all course with published status --- src/roadmap/roadmap.service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/roadmap/roadmap.service.ts b/src/roadmap/roadmap.service.ts index 49fa5c7..2c441f9 100644 --- a/src/roadmap/roadmap.service.ts +++ b/src/roadmap/roadmap.service.ts @@ -8,6 +8,7 @@ import { import { ConfigService } from '@nestjs/config'; import { Course } from 'src/course/course.entity'; import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; +import { CourseStatus } from 'src/shared/enums'; import { createPagination } from 'src/shared/pagination'; import { UserBackgroundService } from 'src/user-background/user-background.service'; import { FindOneOptions, FindOptionsWhere, Repository } from 'typeorm'; @@ -40,6 +41,7 @@ export class RoadmapService { ); const course = await this.courseRepository.find({ relations: this.defaultRelations, + where: { status: CourseStatus.PUBLISHED }, }); try { const requestBody = { From 2f73226c0fddf7f538036353b093864a13733a57 Mon Sep 17 00:00:00 2001 From: ganthepro Date: Fri, 29 Nov 2024 15:56:59 +0700 Subject: [PATCH 149/155] feat: add user switch step in GitHub Actions workflow --- .github/workflows/main.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 303f127..7e9f012 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,6 +13,9 @@ jobs: build-and-deploy: runs-on: self-hosted steps: + - name: Switch User + run: sudo -s + - name: Copy Repository uses: actions/checkout@v4 From c01059d5ad9d1b54b393e76c049528ada0fff55b Mon Sep 17 00:00:00 2001 From: ganthepro Date: Fri, 29 Nov 2024 16:02:06 +0700 Subject: [PATCH 150/155] feat: add API_URL to environment variables in GitHub Actions workflow --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7e9f012..2e05fe4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -41,6 +41,7 @@ jobs: echo "ADMIN_EMAIL=${{ secrets.ADMIN_EMAIL }}" >> .env echo "ADMIN_PASSWORD=${{ secrets.ADMIN_PASSWORD }}" >> .env echo "AI_URL=${{ secrets.AI_URL }}" >> .env + echo "API_URL=${{ secrets.API_URL }}" >> .env cat .env - name: Running Docker Compose From c9d5f4ec347870e841004a714f61c6dbb30a01bf Mon Sep 17 00:00:00 2001 From: ganthepro Date: Fri, 29 Nov 2024 16:03:08 +0700 Subject: [PATCH 151/155] refactor: remove unnecessary user switch step from GitHub Actions workflow --- .github/workflows/main.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2e05fe4..dc8095d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,9 +13,6 @@ jobs: build-and-deploy: runs-on: self-hosted steps: - - name: Switch User - run: sudo -s - - name: Copy Repository uses: actions/checkout@v4 From 6d4fac370d4420afcfd84a1cc55b4de52b6adccc Mon Sep 17 00:00:00 2001 From: ganthepro Date: Fri, 29 Nov 2024 16:48:16 +0700 Subject: [PATCH 152/155] feat: add VOLUMES_PATH to environment variables and update docker-compose volume configuration --- .github/workflows/main.yml | 2 ++ docker-compose.yml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index dc8095d..b8543bf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -39,6 +39,8 @@ jobs: echo "ADMIN_PASSWORD=${{ secrets.ADMIN_PASSWORD }}" >> .env echo "AI_URL=${{ secrets.AI_URL }}" >> .env echo "API_URL=${{ secrets.API_URL }}" >> .env + echo "API_URL=${{ secrets.API_URL }}" >> .env + echo "VOLUMES_PATH=${{ secrets.VOLUMES_PATH }}" >> .env cat .env - name: Running Docker Compose diff --git a/docker-compose.yml b/docker-compose.yml index 72ca334..03bbcbb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,7 +12,7 @@ services: POSTGRES_PASSWORD: ${DB_PASSWORD} POSTGRES_DB: ${DB_DATABASE} volumes: - - ./postgres-data:/var/lib/postgresql/data + - ${VOLUMES_PATH}:/var/lib/postgresql/data healthcheck: test: ['CMD-SHELL', 'pg_isready -U ${DB_USERNAME}'] interval: 10s From 791e35055b360618d85893803fa4222d51993164 Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Fri, 29 Nov 2024 17:51:36 +0700 Subject: [PATCH 153/155] refactor: change chapter summary endpoint and enhance error handling in transcription and summarization methods --- src/chapter/chapter.controller.ts | 2 +- src/chapter/chapter.service.ts | 104 ++++++++++++++------ src/chapter/dtos/transcribe-response.dto.ts | 9 -- 3 files changed, 74 insertions(+), 41 deletions(-) diff --git a/src/chapter/chapter.controller.ts b/src/chapter/chapter.controller.ts index 6d1eafc..06a9542 100644 --- a/src/chapter/chapter.controller.ts +++ b/src/chapter/chapter.controller.ts @@ -262,7 +262,7 @@ export class ChapterController { where: { id }, }); } - @Get('summarize/:id') + @Get(':id/summarize') @ApiResponse({ status: HttpStatus.OK, type: ChapterResponseDto, diff --git a/src/chapter/chapter.service.ts b/src/chapter/chapter.service.ts index 8fe9c4a..a867e1c 100644 --- a/src/chapter/chapter.service.ts +++ b/src/chapter/chapter.service.ts @@ -250,19 +250,34 @@ export class ChapterService { async summarize(id: string): Promise { try { - const transcribeResult = await this.transcribeAudio(id); + const chapter = await this.findOne({ where: { id } }); + if (!chapter) { + throw new NotFoundException('Chapter not found'); + } + + if (chapter.summary != null) { + return chapter; + } - if (!transcribeResult?.transcription) { + const transcribeResult = await this.transcribeAudio(id); + if (!this.isValidTranscription(transcribeResult)) { throw new BadRequestException('Invalid transcription response'); } - const summarize = await this.summarizeChapter(transcribeResult.transcription); + const summarizeResult = await this.summarizeChapter(transcribeResult.transcription); + if (!this.isValidSummary(summarizeResult)) { + throw new BadRequestException('Invalid summary response'); + } - this.chapterRepository.update(id, { summary: summarize.summary }); + await this.chapterRepository.update(id, { summary: summarizeResult.summary }); return await this.findOne({ where: { id } }); + } catch (error) { - console.error('Full error details:', error); + console.error('Summarization error:', error); + if (error instanceof BadRequestException) { + throw error; + } throw new InternalServerErrorException( 'Failed to process audio summarization', { cause: error } @@ -270,76 +285,103 @@ export class ChapterService { } } - async transcribeAudio(id: string): Promise { + private async transcribeAudio(id: string): Promise { + const aiUrl = this.configService.get(GLOBAL_CONFIG.AI_URL); + const apiUrl = this.configService.getOrThrow(GLOBAL_CONFIG.API_URL); + + if (!aiUrl || !apiUrl) { + throw new InternalServerErrorException('Missing configuration for AI or API URL'); + } try { - const transcriptionResponse = await firstValueFrom( - this.httpService.post( - `${this.configService.get(GLOBAL_CONFIG.AI_URL)}/asr`, + const response = await firstValueFrom( + this.httpService.post( + `${aiUrl}/asr`, { - url: `${this.configService.getOrThrow(GLOBAL_CONFIG.API_URL)}/chapter/${id}/video`, + url: `${apiUrl}/chapter/${id}/video`, }, { - headers: { - 'Content-Type': 'application/json' - } + headers: { 'Content-Type': 'application/json' } } ) ); - if (!transcriptionResponse.data) { - throw new BadRequestException('Failed to get transcription'); + if (!response.data) { + throw new BadRequestException('Empty response from transcription service'); } - return transcriptionResponse.data; + return response.data; } catch (error) { + console.error('Transcription error:', error); if (error.response) { throw new InternalServerErrorException( - error.response.data || 'Transcription service error' + error.response.data?.message || 'Transcription service error' ); } - throw new InternalServerErrorException('Error processing transcription request'); } } + private async summarizeChapter(content: string): Promise { + const aiUrl = this.configService.get(GLOBAL_CONFIG.AI_URL); + + if (!aiUrl) { + throw new InternalServerErrorException('Missing AI service URL configuration'); + } + try { - const summarizeResponse = await firstValueFrom( - this.httpService.post( - `${this.configService.get(GLOBAL_CONFIG.AI_URL)}/summarize`, + const response = await firstValueFrom( + this.httpService.post( + `${aiUrl}/summarize`, { content }, { - headers: { - 'Content-Type': 'application/json' - } + headers: { 'Content-Type': 'application/json' } } ) ); - if (!summarizeResponse.data) { - throw new BadRequestException('Failed to get summarization'); + if (!response.data) { + throw new BadRequestException('Empty response from summarization service'); } - let summaryText = summarizeResponse.data.summary; + let summaryText = response.data.summary; + try { const parsedSummary = JSON.parse(summaryText); summaryText = parsedSummary.summary || summaryText; } catch (e) { } - return { - summary: summaryText - }; + return { summary: summaryText }; + } catch (error) { + console.error('Summarization error:', error); if (error.response) { throw new InternalServerErrorException( - error.response.data || 'Summarization service error' + error.response.data?.message || 'Summarization service error' ); } throw new InternalServerErrorException('Error processing summarization request'); } } + + private isValidTranscription(result: TranscribeResponseDto | undefined): result is TranscribeResponseDto { + console.log(result.transcription); + return ( + !!result && + typeof result.transcription === 'string' && + result.transcription.length > 0 + ); + } + + private isValidSummary(result: SummarizeResponseDto | undefined): result is SummarizeResponseDto { + return ( + !!result && + typeof result.summary === 'string' && + result.summary.length > 0 + ); + } private async validateOrderIndex( moduleId: string, orderIndex: number, diff --git a/src/chapter/dtos/transcribe-response.dto.ts b/src/chapter/dtos/transcribe-response.dto.ts index 365238b..def93dc 100644 --- a/src/chapter/dtos/transcribe-response.dto.ts +++ b/src/chapter/dtos/transcribe-response.dto.ts @@ -3,15 +3,6 @@ import { IsNotEmpty, IsString } from "class-validator"; export class TranscribeResponseDto { - @IsNotEmpty() - @IsString() - @ApiProperty({ - description: 'file path', - type: String, - example: 'downloads/video.mp3', - }) - file_path: string; - @IsNotEmpty() @IsString() @ApiProperty({ From 932a950a90897dfd7573c05e81cb8dbf921e1d9e Mon Sep 17 00:00:00 2001 From: Dangkeys <66010360@kmitl.ac.th> Date: Fri, 29 Nov 2024 17:57:19 +0700 Subject: [PATCH 154/155] refactor: remove unnecessary console error logs from chapter service error handling methods --- src/chapter/chapter.service.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/chapter/chapter.service.ts b/src/chapter/chapter.service.ts index a867e1c..bc11b8c 100644 --- a/src/chapter/chapter.service.ts +++ b/src/chapter/chapter.service.ts @@ -274,7 +274,6 @@ export class ChapterService { return await this.findOne({ where: { id } }); } catch (error) { - console.error('Summarization error:', error); if (error instanceof BadRequestException) { throw error; } @@ -313,7 +312,6 @@ export class ChapterService { return response.data; } catch (error) { - console.error('Transcription error:', error); if (error.response) { throw new InternalServerErrorException( error.response.data?.message || 'Transcription service error' @@ -356,7 +354,6 @@ export class ChapterService { return { summary: summaryText }; } catch (error) { - console.error('Summarization error:', error); if (error.response) { throw new InternalServerErrorException( error.response.data?.message || 'Summarization service error' @@ -367,7 +364,6 @@ export class ChapterService { } private isValidTranscription(result: TranscribeResponseDto | undefined): result is TranscribeResponseDto { - console.log(result.transcription); return ( !!result && typeof result.transcription === 'string' && From 04d2c852e8f0d98a622f3230fda465d8ab14c501 Mon Sep 17 00:00:00 2001 From: khris-xp Date: Fri, 29 Nov 2024 20:02:04 +0700 Subject: [PATCH 155/155] fix: remove user occupation public guards --- src/enrollment/enrollment.service.ts | 6 ++++-- src/roadmap/roadmap.service.ts | 7 ++++--- src/user-occupation/user-occupation.controller.ts | 1 - 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/enrollment/enrollment.service.ts b/src/enrollment/enrollment.service.ts index afc4613..289ccb7 100644 --- a/src/enrollment/enrollment.service.ts +++ b/src/enrollment/enrollment.service.ts @@ -35,10 +35,12 @@ export class EnrollmentService { const enrollments = await find({ relations: { user: true, - course: true, + course: { + teacher: true, + category: true, + }, }, }).run(); - return enrollments; } diff --git a/src/roadmap/roadmap.service.ts b/src/roadmap/roadmap.service.ts index 2c441f9..9dfc84a 100644 --- a/src/roadmap/roadmap.service.ts +++ b/src/roadmap/roadmap.service.ts @@ -7,7 +7,6 @@ import { } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Course } from 'src/course/course.entity'; -import { GLOBAL_CONFIG } from 'src/shared/constants/global-config.constant'; import { CourseStatus } from 'src/shared/enums'; import { createPagination } from 'src/shared/pagination'; import { UserBackgroundService } from 'src/user-background/user-background.service'; @@ -132,7 +131,8 @@ export class RoadmapService { .leftJoinAndSelect('roadmap.user', 'user') .leftJoinAndSelect('roadmap.courses', 'courses') .leftJoinAndSelect('courses.teacher', 'teacher') - .leftJoinAndSelect('courses.category', 'category'); + .leftJoinAndSelect('courses.category', 'category') + .orderBy('roadmap.priority', 'ASC'); if (userId) { queryBuilder.andWhere('user.id = :userId', { userId }); @@ -152,6 +152,7 @@ export class RoadmapService { return new PaginatedRoadmapResponseDto(data, total, limit, page); } + async findOne(options: FindOneOptions): Promise { try { const roadmap = await this.roadmapRepository.findOne(options); @@ -192,4 +193,4 @@ export class RoadmapService { throw new InternalServerErrorException(error.message); } } -} +} \ No newline at end of file diff --git a/src/user-occupation/user-occupation.controller.ts b/src/user-occupation/user-occupation.controller.ts index b4b332d..6fd55c6 100644 --- a/src/user-occupation/user-occupation.controller.ts +++ b/src/user-occupation/user-occupation.controller.ts @@ -40,7 +40,6 @@ export class UserOccupationController { @Post() @Roles(Role.ADMIN) - @Public() @ApiResponse({ status: HttpStatus.CREATED, type: UserOccupationResponseDto,