From d55dc4ac914c5d6963cdf16348d7103e8143ee9f Mon Sep 17 00:00:00 2001 From: Ayomide Date: Tue, 23 Jul 2024 01:51:26 +0100 Subject: [PATCH 01/18] chore: added new markdown for setup guide --- README.md | 4 + setup-guide.md | 283 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 287 insertions(+) create mode 100644 setup-guide.md diff --git a/README.md b/README.md index 772447f61..9f56c86e1 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,10 @@ Before you begin, ensure you have the following installed on your machine: - [NestJs](https://docs.nestjs.com) (NestJS' Documentation) - [Git](https://git-scm.com/) +## Setup Guide + #### Detailed guide on setting and starting the Application + - [Setup Guide](setup-guide.md) + ## Contribution Guide ## Getting Started diff --git a/setup-guide.md b/setup-guide.md new file mode 100644 index 000000000..f32fba764 --- /dev/null +++ b/setup-guide.md @@ -0,0 +1,283 @@ +# Quick Start Guide + +## How To Start Up The Application + +### Cloning the repository +- Fork and clone the repository +```bash +git clone https://github.com/hngprojects/hng_boilerplate_nestjs.git +cd hng_boilerplate_nestjs +git checkout chore/database-setup +``` + +### Setting Up the Environment + +### Prerequisites: + + - #### Ensure you have NodeJs and Npm installed. + - You can download and install Node.js from [nodejs.org](https://nodejs.org/). + - Check your installation here: + ```bash + node -v + npm -v + ``` + - #### Ensure you have NestJs installed + - Install the NestJS CLI globally using npm: + + ``` + npm install -g @nestjs/cli + ``` + - Check your installation + ``` + nest -v + ``` + +### Database Setup +- #### Ensure PostgreSQL is installed and running. + +- #### Update the .env file with your database credentials + ``` + PROFILE=local + NODE_ENV=development + PORT=3008 + DB_TYPE=dt_type + DB_USERNAME=your_username + DB_PASSWORD=your_password + DB_HOST=localhost + DB_DATABASE=db_name + DB_ENTITIES=dist/**/*.entity{.ts,.js} + DB_MIGRATIONS=dist/db/migrations/*{.ts,.js} + ``` + +### Running The Application Locally + - Install all dependencies + ``` + npm install + ``` + - Start the application in dev mode + ``` + npm run start:dev + ``` + +### Create And Apply Migration Files +- #### Migration files should be placed in the `db/migration` directory. +- #### Generate migration files automatically with typeorm + ``` + npm run migration:generate + ``` +- #### To manually create migration file + ``` + npm run migration:create + ``` +- #### Run migration + ``` + npm run migration:run + ``` + + + #### Create Sample Data + - The seed data is run once when app starts + +## Testing the Endpoint + +### You can test the endpoint using curl or Postman +- Using Curl +``` +curl -X GET http://localhost:3008/api/v1/users/ +``` + +- Using Postman + * Open Postman. + * Create a new GET request to http://localhost:3008/api/v1/users. + * Send the request and verify the response. +- Expected Response +```json + { + "id": "d6aa5dc9-a1c3-4516-ad4e-31d169893510", + "first_name": "John", + "last_name": "Smith", + "email": "john.smith@example.com", + "password": "$2b$10$lOsaGJVjYxxZsVQ2WNsiwe./MEu.MEp2QiXKAS1FwP3gQtctOM2tG", + "is_active": null, + "attempts_left": null, + "time_left": null, + "created_at": "2024-07-20T13:08:28.273Z", + "updated_at": "2024-07-20T13:08:28.523Z", + "profile": { + "id": "6f8984f8-682a-4bb9-a618-6b14ea109bd3", + "username": "johnsmith", + "bio": "bio data", + "phone": "1234567890", + "avatar_image": "image.png" + }, + "products": [ + { + "id": "61c30739-1b7e-447a-b58e-3b05e0e7c0d3", + "product_name": "Product 1", + "product_price": 100, + "description": "Description 1" + } + ], + "organisations": [ + { + "org_id": "c52d7c25-8632-4b08-91d1-1f9a837f1861", + "org_name": "Org 1", + "description": "Description 1" + }, + { + "org_id": "4b42ea8e-e1e2-407e-88cb-9893c05a4167", + "org_name": "Org 2", + "description": "Description 2" + } + ] + } +``` + +- There are 2 default users created when you start the application. +- To get the sample data, use this route after starting the application: +``` +http://localhost:3008/api/v1/seed/users +``` +- Also, you can get the users by signing in to your database and query the users table using: +`SELECT * FROM users;`# Quick Start Guide + +## How To Start Up The Application + +### Cloning the repository +- Fork and clone the repository +```bash +git clone https://github.com/hngprojects/hng_boilerplate_nestjs.git +cd hng_boilerplate_nestjs +git checkout chore/database-setup +``` + +### Setting Up the Environment + +### Prerequisites: + + - #### Ensure you have NodeJs and Npm installed. + - You can download and install Node.js from [nodejs.org](https://nodejs.org/). + - Check your installation here: + ```bash + node -v + npm -v + ``` + - #### Ensure you have NestJs installed + - Install the NestJS CLI globally using npm: + + ``` + npm install -g @nestjs/cli + ``` + - Check your installation + ``` + nest -v + ``` + +### Database Setup +- #### Ensure PostgreSQL is installed and running. + +- #### Update the .env file with your database credentials + ``` + PROFILE=local + NODE_ENV=development + PORT=3008 + DB_TYPE=dt_type + DB_USERNAME=your_username + DB_PASSWORD=your_password + DB_HOST=localhost + DB_DATABASE=db_name + DB_ENTITIES=dist/**/*.entity{.ts,.js} + DB_MIGRATIONS=dist/db/migrations/*{.ts,.js} + ``` + +### Running The Application Locally + - Install all dependencies + ``` + npm install + ``` + - Start the application in dev mode + ``` + npm run start:dev + ``` + +### Create And Apply Migration Files +- #### Migration files should be placed in the `db/migration` directory. +- #### Generate migration files automatically with typeorm + ``` + npm run migration:generate + ``` +- #### To manually create migration file + ``` + npm run migration:create + ``` +- #### Run migration + ``` + npm run migration:run + ``` + + + #### Create Sample Data + - The seed data is run once when app starts + +## Testing the Endpoint + +### You can test the endpoint using curl or Postman +- Using Curl +``` +curl -X GET http://localhost:3008/api/v1/users/ +``` + +- Using Postman + * Open Postman. + * Create a new GET request to http://localhost:3008/api/v1/users. + * Send the request and verify the response. +- Expected Response +```json + { + "id": "d6aa5dc9-a1c3-4516-ad4e-31d169893510", + "first_name": "John", + "last_name": "Smith", + "email": "john.smith@example.com", + "password": "$2b$10$lOsaGJVjYxxZsVQ2WNsiwe./MEu.MEp2QiXKAS1FwP3gQtctOM2tG", + "is_active": null, + "attempts_left": null, + "time_left": null, + "created_at": "2024-07-20T13:08:28.273Z", + "updated_at": "2024-07-20T13:08:28.523Z", + "profile": { + "id": "6f8984f8-682a-4bb9-a618-6b14ea109bd3", + "username": "johnsmith", + "bio": "bio data", + "phone": "1234567890", + "avatar_image": "image.png" + }, + "products": [ + { + "id": "61c30739-1b7e-447a-b58e-3b05e0e7c0d3", + "product_name": "Product 1", + "product_price": 100, + "description": "Description 1" + } + ], + "organisations": [ + { + "org_id": "c52d7c25-8632-4b08-91d1-1f9a837f1861", + "org_name": "Org 1", + "description": "Description 1" + }, + { + "org_id": "4b42ea8e-e1e2-407e-88cb-9893c05a4167", + "org_name": "Org 2", + "description": "Description 2" + } + ] + } +``` + +- There are 2 default users created when you start the application. +- To get the sample data, use this route after starting the application: +``` +http://localhost:3008/api/v1/seed/users +``` +- Also, you can get the users by signing in to your database and query the users table using: +`SELECT * FROM users;` \ No newline at end of file From 246e2334db53e32e1c4b647850e02fb8b8d3b911 Mon Sep 17 00:00:00 2001 From: Ayomide Date: Tue, 23 Jul 2024 21:23:58 +0100 Subject: [PATCH 02/18] feat: update organization endpoint --- db/migrations/1721761856231-migration.ts | 41 ++++++++++ package-lock.json | 39 ++++++++-- package.json | 5 +- src/app.module.ts | 2 + src/database/seeding/seeding.service.ts | 38 ++++++++- src/main.ts | 4 +- .../dto/create-organisation.dto.ts | 1 + .../dto/update-organisation.dto.ts | 49 ++++++++++++ .../entities/org-preferences.entity.ts | 15 ++++ .../entities/organisation.entity.ts | 51 ++++++++++++ .../organisation/organisation.controller.ts | 38 +++++++++ .../organisation/organisation.module.ts | 12 +++ .../organisation/organisation.service.ts | 73 +++++++++++++++++ .../tests/organisation.service.spec.ts | 78 +++++++++++++++++++ src/modules/user/entities/user.entity.ts | 22 +++++- .../inteceptors/response.interceptor.ts | 53 +++++++++++++ 16 files changed, 510 insertions(+), 11 deletions(-) create mode 100644 db/migrations/1721761856231-migration.ts create mode 100644 src/modules/organisation/dto/create-organisation.dto.ts create mode 100644 src/modules/organisation/dto/update-organisation.dto.ts create mode 100644 src/modules/organisation/entities/org-preferences.entity.ts create mode 100644 src/modules/organisation/entities/organisation.entity.ts create mode 100644 src/modules/organisation/organisation.controller.ts create mode 100644 src/modules/organisation/organisation.module.ts create mode 100644 src/modules/organisation/organisation.service.ts create mode 100644 src/modules/organisation/tests/organisation.service.spec.ts create mode 100644 src/shared/inteceptors/response.interceptor.ts diff --git a/db/migrations/1721761856231-migration.ts b/db/migrations/1721761856231-migration.ts new file mode 100644 index 000000000..e06dc084c --- /dev/null +++ b/db/migrations/1721761856231-migration.ts @@ -0,0 +1,41 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class Migration1721761856231 implements MigrationInterface { + name = 'Migration1721761856231'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "abstract_base_entity" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_723c213727b1038d476972ce2f2" PRIMARY KEY ("id"))` + ); + await queryRunner.query(`CREATE TYPE "public"."user_user_type_enum" AS ENUM('super_admin', 'admin', 'vendor')`); + await queryRunner.query( + `CREATE TABLE "user" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "first_name" character varying NOT NULL, "last_name" character varying NOT NULL, "email" character varying NOT NULL, "password" character varying NOT NULL, "is_active" boolean, "attempts_left" integer, "time_left" integer, "user_type" "public"."user_user_type_enum" NOT NULL DEFAULT 'vendor', CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"), CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))` + ); + await queryRunner.query( + `CREATE TABLE "organisation_preference" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "name" character varying NOT NULL, "value" character varying NOT NULL, "organisationId" uuid NOT NULL, CONSTRAINT "PK_3149ecbe39a50d9b76f09b9dd44" PRIMARY KEY ("id"))` + ); + await queryRunner.query( + `CREATE TABLE "organisation" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "name" character varying NOT NULL, "description" text NOT NULL, "email" character varying NOT NULL, "industry" character varying NOT NULL, "type" character varying NOT NULL, "country" character varying NOT NULL, "address" text NOT NULL, "state" character varying NOT NULL, "isDeleted" boolean NOT NULL DEFAULT false, "ownerId" uuid NOT NULL, "creatorId" uuid NOT NULL, CONSTRAINT "UQ_a795e00e9d60fc3c2683caac33b" UNIQUE ("email"), CONSTRAINT "PK_c725ae234ef1b74cce43d2d00c1" PRIMARY KEY ("id"))` + ); + await queryRunner.query( + `ALTER TABLE "organisation_preference" ADD CONSTRAINT "FK_2de786f3f89e650581916d67f86" FOREIGN KEY ("organisationId") REFERENCES "organisation"("id") ON DELETE CASCADE ON UPDATE NO ACTION` + ); + await queryRunner.query( + `ALTER TABLE "organisation" ADD CONSTRAINT "FK_d8df3e440ba45237db29bae7631" FOREIGN KEY ("ownerId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ); + await queryRunner.query( + `ALTER TABLE "organisation" ADD CONSTRAINT "FK_87890d319ae77ea7ae5ec2586df" FOREIGN KEY ("creatorId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION` + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "organisation" DROP CONSTRAINT "FK_87890d319ae77ea7ae5ec2586df"`); + await queryRunner.query(`ALTER TABLE "organisation" DROP CONSTRAINT "FK_d8df3e440ba45237db29bae7631"`); + await queryRunner.query(`ALTER TABLE "organisation_preference" DROP CONSTRAINT "FK_2de786f3f89e650581916d67f86"`); + await queryRunner.query(`DROP TABLE "organisation"`); + await queryRunner.query(`DROP TABLE "organisation_preference"`); + await queryRunner.query(`DROP TABLE "user"`); + await queryRunner.query(`DROP TYPE "public"."user_user_type_enum"`); + await queryRunner.query(`DROP TABLE "abstract_base_entity"`); + } +} diff --git a/package-lock.json b/package-lock.json index d7d7ea108..23482d1ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,6 @@ "passport-jwt": "^4.0.1", "pg": "^8.12.0", "reflect-metadata": "^0.2.2", - "rxjs": "^7.8.1", "typeorm": "^0.3.20", "typeorm-extension": "^3.5.1", "types-joi": "^2.1.0" @@ -48,6 +47,7 @@ "eslint": "^8.42.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-import": "^2.29.1", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-prettier": "^5.0.0", "husky": "^9.0.11", "jest": "^29.5.0", @@ -59,7 +59,7 @@ "ts-node": "^10.9.1", "ts-node-dev": "^2.0.0", "tsconfig-paths": "^4.2.0", - "typescript": "^5.1.3" + "typescript": "^5.5.4" } }, "node_modules/@ampproject/remapping": { @@ -4108,6 +4108,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "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 + }, "node_modules/atomic-sleep": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", @@ -5317,6 +5329,23 @@ "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, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -12561,9 +12590,9 @@ "integrity": "sha512-wdvZWNhDx9syXdes3V+YH0KLRNiwGsg7itbjL27truN1Av3YvnJDc3HGs9kbTpfzi3vV2q0scM2y6iSkm9nriQ==" }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "devOptional": true, "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 6312e7629..21f6d3853 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ "passport-jwt": "^4.0.1", "pg": "^8.12.0", "reflect-metadata": "^0.2.2", - "rxjs": "^7.8.1", "typeorm": "^0.3.20", "typeorm-extension": "^3.5.1", "types-joi": "^2.1.0" @@ -82,7 +81,7 @@ "ts-node": "^10.9.1", "ts-node-dev": "^2.0.0", "tsconfig-paths": "^4.2.0", - "typescript": "^5.1.3" + "typescript": "^5.5.4" }, "jest": { "moduleFileExtensions": [ @@ -104,4 +103,4 @@ "lint-staged": { "**/*": "prettier --write --ignore-unknown" } -} \ No newline at end of file +} diff --git a/src/app.module.ts b/src/app.module.ts index 8a65ac9c8..612ef729a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -7,6 +7,7 @@ import { LoggerModule } from 'nestjs-pino'; import { TypeOrmModule } from '@nestjs/typeorm'; import dataSource from './database/data-source'; import { SeedingModule } from './database/seeding/seeding.module'; +import { OrganisationModule } from './modules/organisation/organisation.module'; import HealthController from './health.controller'; import { AuthModule } from './modules/auth/auth.module'; import { UserModule } from './modules/user/user.module'; @@ -57,6 +58,7 @@ import authConfig from 'config/auth.config'; SeedingModule, AuthModule, UserModule, + OrganisationModule, ], controllers: [HealthController], }) diff --git a/src/database/seeding/seeding.service.ts b/src/database/seeding/seeding.service.ts index 40bcab8f4..cd5c61f16 100644 --- a/src/database/seeding/seeding.service.ts +++ b/src/database/seeding/seeding.service.ts @@ -1,6 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { User } from '../../modules/user/entities/user.entity'; +import { Organisation } from '../../modules/organisation/entities/organisation.entity'; @Injectable() export class SeedingService { @@ -8,6 +9,7 @@ export class SeedingService { async seedDatabase() { const userRepository = this.dataSource.getRepository(User); + const organisationRepository = this.dataSource.getRepository(Organisation); try { const existingUsers = await userRepository.count(); @@ -41,6 +43,40 @@ export class SeedingService { throw new Error('Failed to create all users'); } + const or1 = organisationRepository.create({ + name: 'Org 1', + description: 'Description 1', + email: 'test1@email.com', + industry: 'industry1', + type: 'type1', + country: 'country1', + state: 'state1', + address: 'address1', + owner: savedUsers[0], + creator: savedUsers[0], + isDeleted: false, + }); + + const or2 = organisationRepository.create({ + name: 'Org 2', + description: 'Description 2', + email: 'test2@email.com', + industry: 'industry2', + type: 'type2', + country: 'country2', + state: 'state2', + address: 'address2', + owner: savedUsers[0], + creator: savedUsers[0], + isDeleted: false, + }); + + await organisationRepository.save([or1, or2]); + const savedOrganisations = await organisationRepository.find(); + if (savedOrganisations.length !== 2) { + throw new Error('Failed to create all organisations'); + } + await queryRunner.commitTransaction(); } catch (error) { await queryRunner.rollbackTransaction(); @@ -55,7 +91,7 @@ export class SeedingService { async getUsers(): Promise { try { - return this.dataSource.getRepository(User).find({ relations: ['profile', 'products', 'organisations'] }); + return this.dataSource.getRepository(User).find({ relations: ['organisations'] }); } catch (error) { console.error('Error fetching users:', error.message); throw error; diff --git a/src/main.ts b/src/main.ts index 77596ba8a..701b8ee0a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,6 +7,7 @@ import { ConfigService } from '@nestjs/config'; import { DataSource } from 'typeorm'; import dataSource, { initializeDataSource } from './database/data-source'; import { SeedingService } from './database/seeding/seeding.service'; +import { ResponseInterceptor } from './shared/inteceptors/response.interceptor'; async function bootstrap() { const app = await NestFactory.create(AppModule, { bufferLogs: true }); @@ -29,7 +30,8 @@ async function bootstrap() { app.enable('trust proxy'); app.useLogger(logger); app.enableCors(); - app.setGlobalPrefix('api/v1', { exclude: ["/", "health"] }); + app.setGlobalPrefix('api/v1', { exclude: ['/', 'health'] }); + app.useGlobalInterceptors(new ResponseInterceptor()); // TODO: set options for swagger docs const options = new DocumentBuilder() diff --git a/src/modules/organisation/dto/create-organisation.dto.ts b/src/modules/organisation/dto/create-organisation.dto.ts new file mode 100644 index 000000000..92183745a --- /dev/null +++ b/src/modules/organisation/dto/create-organisation.dto.ts @@ -0,0 +1 @@ +export class CreateOrganisationDto {} diff --git a/src/modules/organisation/dto/update-organisation.dto.ts b/src/modules/organisation/dto/update-organisation.dto.ts new file mode 100644 index 000000000..a15d52999 --- /dev/null +++ b/src/modules/organisation/dto/update-organisation.dto.ts @@ -0,0 +1,49 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateOrganisationDto } from './create-organisation.dto'; +import { IsBoolean, IsEmail, IsOptional, IsString, MinLength } from 'class-validator'; +import { User } from '../../user/entities/user.entity'; + +export class UpdateOrganisationDto extends PartialType(CreateOrganisationDto) { + @IsString() + @MinLength(2, { message: 'Organization name must be at least 2 characters long' }) + @IsOptional() + name?: string; + + @IsString() + @IsOptional() + description?: string; + + @IsEmail() + @IsOptional() + email?: string; + + @IsString() + @IsOptional() + industry?: string; + + @IsString() + @IsOptional() + type?: string; + + @IsString() + @IsOptional() + country?: string; + + @IsString() + @IsOptional() + address?: string; + + @IsOptional() + owner?: User; + + @IsString() + @IsOptional() + state?: string; + + @IsOptional() + creator?: User; + + @IsBoolean() + @IsOptional() + isDeleted?: boolean; +} diff --git a/src/modules/organisation/entities/org-preferences.entity.ts b/src/modules/organisation/entities/org-preferences.entity.ts new file mode 100644 index 000000000..cd857a26b --- /dev/null +++ b/src/modules/organisation/entities/org-preferences.entity.ts @@ -0,0 +1,15 @@ +import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne } from 'typeorm'; +import { AbstractBaseEntity } from './../../../entities/base.entity'; +import { Organisation } from './organisation.entity'; + +@Entity() +export class OrganisationPreference extends AbstractBaseEntity { + @Column({ nullable: false }) + name: string; + + @Column({ nullable: false }) + value: string; + + @ManyToOne(() => Organisation, organisation => organisation.preferences, { nullable: false, onDelete: 'CASCADE' }) + organisation: Organisation; +} diff --git a/src/modules/organisation/entities/organisation.entity.ts b/src/modules/organisation/entities/organisation.entity.ts new file mode 100644 index 000000000..ab3b018ed --- /dev/null +++ b/src/modules/organisation/entities/organisation.entity.ts @@ -0,0 +1,51 @@ +import { + Entity, + PrimaryGeneratedColumn, + Column, + CreateDateColumn, + UpdateDateColumn, + ManyToOne, + OneToMany, +} from 'typeorm'; +import { User } from '../../user/entities/user.entity'; +import { OrganisationPreference } from './org-preferences.entity'; +import { AbstractBaseEntity } from '../../../entities/base.entity'; + +@Entity() +export class Organisation extends AbstractBaseEntity { + @Column({ nullable: false }) + name: string; + + @Column('text', { nullable: false }) + description: string; + + @Column({ unique: true, nullable: false }) + email: string; + + @Column({ nullable: false }) + industry: string; + + @Column({ nullable: false }) + type: string; + + @Column({ nullable: false }) + country: string; + + @Column('text', { nullable: false }) + address: string; + + @ManyToOne(() => User, user => user.owned_organisations, { nullable: false }) + owner: User; + + @Column({ nullable: false }) + state: string; + + @ManyToOne(() => User, user => user.created_organisations, { nullable: false }) + creator: User; + + @Column('boolean', { default: false, nullable: false }) + isDeleted: boolean; + + @OneToMany(() => OrganisationPreference, preference => preference.organisation) + preferences: OrganisationPreference[]; +} diff --git a/src/modules/organisation/organisation.controller.ts b/src/modules/organisation/organisation.controller.ts new file mode 100644 index 000000000..dc5afa025 --- /dev/null +++ b/src/modules/organisation/organisation.controller.ts @@ -0,0 +1,38 @@ +import { Controller, Get, Post, Body, Patch, Param, Delete, HttpException } from '@nestjs/common'; +import { OrganisationService } from './organisation.service'; +import { CreateOrganisationDto } from './dto/create-organisation.dto'; +import { UpdateOrganisationDto } from './dto/update-organisation.dto'; + +@Controller('organisation') +export class OrganisationController { + constructor(private readonly organisationService: OrganisationService) {} + + @Post() + create(@Body() createOrganisationDto: CreateOrganisationDto) { + return this.organisationService.create(createOrganisationDto); + } + + @Get() + findAll() { + return this.organisationService.findAll(); + } + + @Get(':id') + findOne(@Param('id') id: string) { + return this.organisationService.findOne(+id); + } + + @Patch(':id') + async update(@Param('id') id: string, @Body() updateOrganisationDto: UpdateOrganisationDto) { + try { + return await this.organisationService.update(id, updateOrganisationDto); + } catch (error) { + throw new HttpException(error.message, error.status || 500); + } + } + + @Delete(':id') + remove(@Param('id') id: string) { + return this.organisationService.remove(+id); + } +} diff --git a/src/modules/organisation/organisation.module.ts b/src/modules/organisation/organisation.module.ts new file mode 100644 index 000000000..df276e6a3 --- /dev/null +++ b/src/modules/organisation/organisation.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { OrganisationService } from './organisation.service'; +import { OrganisationController } from './organisation.controller'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Organisation } from './entities/organisation.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([Organisation])], + controllers: [OrganisationController], + providers: [OrganisationService], +}) +export class OrganisationModule {} diff --git a/src/modules/organisation/organisation.service.ts b/src/modules/organisation/organisation.service.ts new file mode 100644 index 000000000..8fa95897b --- /dev/null +++ b/src/modules/organisation/organisation.service.ts @@ -0,0 +1,73 @@ +import { + BadRequestException, + HttpException, + HttpStatus, + Injectable, + InternalServerErrorException, + NotFoundException, +} from '@nestjs/common'; +import { CreateOrganisationDto } from './dto/create-organisation.dto'; +import { UpdateOrganisationDto } from './dto/update-organisation.dto'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { Organisation } from './entities/organisation.entity'; + +@Injectable() +export class OrganisationService { + constructor( + @InjectRepository(Organisation) + private organisationRepository: Repository + ) {} + + create(createOrganisationDto: CreateOrganisationDto) { + return 'This action adds a new organisation'; + } + + async findAll() { + try { + const orgs = await this.organisationRepository.find(); + console.log(orgs); + + return orgs; + } catch (error) { + console.log(error.message); + } + throw new Error(`Internal error`); + } + + findOne(id: number) { + return `This action returns a #${id} organisation`; + } + + async update( + id: string, + updateOrganisationDto: UpdateOrganisationDto + ): Promise<{ message: string; org: Organisation }> { + try { + const org = await this.organisationRepository.findOneBy({ id }); + + if (!org) { + throw new NotFoundException('Organization not found'); + } + + await this.organisationRepository.update(id, updateOrganisationDto); + + const updatedOrg = await this.organisationRepository.findOneBy({ id }); + + if (!updatedOrg) { + throw new BadRequestException('Error updating organization'); + } + + return { message: 'Product successfully updated', org: updatedOrg }; + } catch (error) { + if (error instanceof NotFoundException || error instanceof BadRequestException) { + throw error; + } + throw new InternalServerErrorException(`An internal server error occurred: ${error.message}`); + } + } + + remove(id: number) { + return `This action removes a #${id} organisation`; + } +} diff --git a/src/modules/organisation/tests/organisation.service.spec.ts b/src/modules/organisation/tests/organisation.service.spec.ts new file mode 100644 index 000000000..d883792c4 --- /dev/null +++ b/src/modules/organisation/tests/organisation.service.spec.ts @@ -0,0 +1,78 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { OrganisationService } from '../organisation.service'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Organisation } from '../entities/organisation.entity'; +import { Repository } from 'typeorm'; +import { NotFoundException, BadRequestException, InternalServerErrorException } from '@nestjs/common'; + +describe('OrganisationService', () => { + let service: OrganisationService; + let repository: Repository; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + OrganisationService, + { + provide: getRepositoryToken(Organisation), + useClass: Repository, + }, + ], + }).compile(); + + service = module.get(OrganisationService); + repository = module.get>(getRepositoryToken(Organisation)); + }); + + describe('update', () => { + it('should update an organisation successfully', async () => { + const id = '1'; + const updateOrganisationDto = { org_name: 'New Name', description: 'Updated Description' }; + const organisation = new Organisation(); + + jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(organisation); + jest.spyOn(repository, 'update').mockResolvedValueOnce({ affected: 1 } as any); + jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce({ ...organisation, ...updateOrganisationDto }); + + const result = await service.update(id, updateOrganisationDto); + + expect(result).toEqual({ + response: { + status: 'successful', + message: 'Product successfully updated', + status_code: 200, + org: { ...organisation, ...updateOrganisationDto }, + }, + }); + }); + + it('should throw NotFoundException if organisation not found', async () => { + const id = '1'; + const updateOrganisationDto = { org_name: 'New Name', description: 'Updated Description' }; + + jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(null); + + await expect(service.update(id, updateOrganisationDto)).rejects.toThrow(NotFoundException); + }); + + it('should throw BadRequestException if update fails', async () => { + const id = '1'; + const updateOrganisationDto = { org_name: 'New Name', description: 'Updated Description' }; + const organisation = new Organisation(); + + jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(organisation); + jest.spyOn(repository, 'update').mockResolvedValueOnce({ affected: 0 } as any); + + await expect(service.update(id, updateOrganisationDto)).rejects.toThrow(BadRequestException); + }); + + it('should throw InternalServerErrorException if an unexpected error occurs', async () => { + const id = '1'; + const updateOrganisationDto = { org_name: 'New Name', description: 'Updated Description' }; + + jest.spyOn(repository, 'findOneBy').mockRejectedValueOnce(new Error('Unexpected error')); + + await expect(service.update(id, updateOrganisationDto)).rejects.toThrow(InternalServerErrorException); + }); + }); +}); diff --git a/src/modules/user/entities/user.entity.ts b/src/modules/user/entities/user.entity.ts index 944fe783e..d0125b5c4 100644 --- a/src/modules/user/entities/user.entity.ts +++ b/src/modules/user/entities/user.entity.ts @@ -1,6 +1,13 @@ -import { BeforeInsert, Column, Entity } from 'typeorm'; +import { BeforeInsert, Column, Entity, OneToMany } from 'typeorm'; import * as bcrypt from 'bcrypt'; import { AbstractBaseEntity } from '../../../entities/base.entity'; +import { Organisation } from '../../organisation/entities/organisation.entity'; + +export enum UserType { + SUPER_ADMIN = 'super_admin', + ADMIN = 'admin', + USER = 'vendor', +} @Entity() export class User extends AbstractBaseEntity { @@ -25,6 +32,19 @@ export class User extends AbstractBaseEntity { @Column({ nullable: true }) time_left: number; + @Column({ + type: 'enum', + enum: UserType, + default: UserType.USER, + }) + user_type: UserType; + + @OneToMany(() => Organisation, organisation => organisation.owner) + owned_organisations: Organisation[]; + + @OneToMany(() => Organisation, organisation => organisation.creator) + created_organisations: Organisation[]; + @BeforeInsert() async hashPassword() { this.password = await bcrypt.hash(this.password, 10); diff --git a/src/shared/inteceptors/response.interceptor.ts b/src/shared/inteceptors/response.interceptor.ts new file mode 100644 index 000000000..b84edf1ce --- /dev/null +++ b/src/shared/inteceptors/response.interceptor.ts @@ -0,0 +1,53 @@ +import { Injectable, NestInterceptor, ExecutionContext, CallHandler, HttpException, HttpStatus } from '@nestjs/common'; +import { Observable, throwError } from 'rxjs'; +import { catchError, map } from 'rxjs/operators'; + +@Injectable() +export class ResponseInterceptor implements NestInterceptor { + intercept(context: ExecutionContext, next: CallHandler): Observable { + return next.handle().pipe( + map((res: any) => this.responseHandler(res, context)), + catchError((err: HttpException) => throwError(() => this.errorHandler(err, context))) + ); + } + + errorHandler(exception: HttpException, context: ExecutionContext) { + const ctx = context.switchToHttp(); + const response = ctx.getResponse(); + const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; + const exceptionResponse: any = exception.getResponse(); + + let errorMessage = 'An error occurred'; + + // Extract error message from exceptionResponse + if (typeof exceptionResponse === 'object' && 'message' in exceptionResponse) { + if (Array.isArray(exceptionResponse.message)) { + errorMessage = exceptionResponse.message.join(', '); + } else { + errorMessage = exceptionResponse.message; + } + } + + response.status(status).json({ + status: false, + status_code: status, + error: exceptionResponse.error || exceptionResponse, + message: errorMessage, + }); + } + + responseHandler(res: any, context: ExecutionContext) { + const ctx = context.switchToHttp(); + const response = ctx.getResponse(); + const status_code = response.statusCode; + + const { message, ...data } = res; + + return { + status: true, + status_code, + message, + ...data, + }; + } +} From da0dfbb5fe751667b9a4cc94dcfa74a0a3f58490 Mon Sep 17 00:00:00 2001 From: Ayomide Date: Tue, 23 Jul 2024 22:10:48 +0100 Subject: [PATCH 03/18] feat: update organization endpoint --- .../tests/organisation.service.spec.ts | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/modules/organisation/tests/organisation.service.spec.ts b/src/modules/organisation/tests/organisation.service.spec.ts index d883792c4..9a42ff7a7 100644 --- a/src/modules/organisation/tests/organisation.service.spec.ts +++ b/src/modules/organisation/tests/organisation.service.spec.ts @@ -27,7 +27,7 @@ describe('OrganisationService', () => { describe('update', () => { it('should update an organisation successfully', async () => { const id = '1'; - const updateOrganisationDto = { org_name: 'New Name', description: 'Updated Description' }; + const updateOrganisationDto = { name: 'New Name', description: 'Updated Description' }; const organisation = new Organisation(); jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(organisation); @@ -37,18 +37,14 @@ describe('OrganisationService', () => { const result = await service.update(id, updateOrganisationDto); expect(result).toEqual({ - response: { - status: 'successful', - message: 'Product successfully updated', - status_code: 200, - org: { ...organisation, ...updateOrganisationDto }, - }, + message: 'Product successfully updated', + org: { ...organisation, ...updateOrganisationDto }, }); }); it('should throw NotFoundException if organisation not found', async () => { const id = '1'; - const updateOrganisationDto = { org_name: 'New Name', description: 'Updated Description' }; + const updateOrganisationDto = { name: 'New Name', description: 'Updated Description' }; jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(null); @@ -57,18 +53,19 @@ describe('OrganisationService', () => { it('should throw BadRequestException if update fails', async () => { const id = '1'; - const updateOrganisationDto = { org_name: 'New Name', description: 'Updated Description' }; + const updateOrganisationDto = { name: 'New Name', description: 'Updated Description' }; const organisation = new Organisation(); jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(organisation); jest.spyOn(repository, 'update').mockResolvedValueOnce({ affected: 0 } as any); + jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(null); // Trigger BadRequestException await expect(service.update(id, updateOrganisationDto)).rejects.toThrow(BadRequestException); }); it('should throw InternalServerErrorException if an unexpected error occurs', async () => { const id = '1'; - const updateOrganisationDto = { org_name: 'New Name', description: 'Updated Description' }; + const updateOrganisationDto = { name: 'New Name', description: 'Updated Description' }; jest.spyOn(repository, 'findOneBy').mockRejectedValueOnce(new Error('Unexpected error')); From c93b430933c1328c2bd217190fed1de6185cb3ae Mon Sep 17 00:00:00 2001 From: Ayomide Date: Tue, 23 Jul 2024 22:45:21 +0100 Subject: [PATCH 04/18] feat: update organization endpoint --- db/migrations/1721761856231-migration.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/db/migrations/1721761856231-migration.ts b/db/migrations/1721761856231-migration.ts index e06dc084c..802c94eb6 100644 --- a/db/migrations/1721761856231-migration.ts +++ b/db/migrations/1721761856231-migration.ts @@ -4,9 +4,6 @@ export class Migration1721761856231 implements MigrationInterface { name = 'Migration1721761856231'; public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `CREATE TABLE "abstract_base_entity" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_723c213727b1038d476972ce2f2" PRIMARY KEY ("id"))` - ); await queryRunner.query(`CREATE TYPE "public"."user_user_type_enum" AS ENUM('super_admin', 'admin', 'vendor')`); await queryRunner.query( `CREATE TABLE "user" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "first_name" character varying NOT NULL, "last_name" character varying NOT NULL, "email" character varying NOT NULL, "password" character varying NOT NULL, "is_active" boolean, "attempts_left" integer, "time_left" integer, "user_type" "public"."user_user_type_enum" NOT NULL DEFAULT 'vendor', CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"), CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))` From 3c429c986e26d33332d3dca6cf3dd51d9a691a7e Mon Sep 17 00:00:00 2001 From: Ayomide Date: Tue, 23 Jul 2024 23:13:50 +0100 Subject: [PATCH 05/18] chore: added swagger documentation --- db/migrations/1721761856231-migration.ts | 1 - .../organisation/dto/update-organisation.dto.ts | 10 +++++++++- src/modules/organisation/organisation.controller.ts | 12 +++++++++++- .../organisation/tests/organisation.service.spec.ts | 2 +- src/shared/inteceptors/response.interceptor.ts | 1 - 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/db/migrations/1721761856231-migration.ts b/db/migrations/1721761856231-migration.ts index 802c94eb6..dbfaeafc5 100644 --- a/db/migrations/1721761856231-migration.ts +++ b/db/migrations/1721761856231-migration.ts @@ -33,6 +33,5 @@ export class Migration1721761856231 implements MigrationInterface { await queryRunner.query(`DROP TABLE "organisation_preference"`); await queryRunner.query(`DROP TABLE "user"`); await queryRunner.query(`DROP TYPE "public"."user_user_type_enum"`); - await queryRunner.query(`DROP TABLE "abstract_base_entity"`); } } diff --git a/src/modules/organisation/dto/update-organisation.dto.ts b/src/modules/organisation/dto/update-organisation.dto.ts index a15d52999..49cc7d202 100644 --- a/src/modules/organisation/dto/update-organisation.dto.ts +++ b/src/modules/organisation/dto/update-organisation.dto.ts @@ -1,14 +1,22 @@ -import { PartialType } from '@nestjs/swagger'; import { CreateOrganisationDto } from './create-organisation.dto'; import { IsBoolean, IsEmail, IsOptional, IsString, MinLength } from 'class-validator'; import { User } from '../../user/entities/user.entity'; +import { ApiProperty, PartialType } from '@nestjs/swagger'; export class UpdateOrganisationDto extends PartialType(CreateOrganisationDto) { + @ApiProperty({ + example: "CodeGhinux's Organisation", + description: 'Name of organisation', + }) @IsString() @MinLength(2, { message: 'Organization name must be at least 2 characters long' }) @IsOptional() name?: string; + @ApiProperty({ + example: "CodeGhinux's Organisation Description", + description: 'description of organisation', + }) @IsString() @IsOptional() description?: string; diff --git a/src/modules/organisation/organisation.controller.ts b/src/modules/organisation/organisation.controller.ts index dc5afa025..d95fc40e7 100644 --- a/src/modules/organisation/organisation.controller.ts +++ b/src/modules/organisation/organisation.controller.ts @@ -2,7 +2,10 @@ import { Controller, Get, Post, Body, Patch, Param, Delete, HttpException } from import { OrganisationService } from './organisation.service'; import { CreateOrganisationDto } from './dto/create-organisation.dto'; import { UpdateOrganisationDto } from './dto/update-organisation.dto'; +import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +@ApiBearerAuth() +@ApiTags('Organisation') @Controller('organisation') export class OrganisationController { constructor(private readonly organisationService: OrganisationService) {} @@ -22,10 +25,17 @@ export class OrganisationController { return this.organisationService.findOne(+id); } + @ApiOperation({ summary: 'Update Organisation' }) + @ApiResponse({ + status: 200, + description: 'The found record', + type: UpdateOrganisationDto, + }) @Patch(':id') async update(@Param('id') id: string, @Body() updateOrganisationDto: UpdateOrganisationDto) { try { - return await this.organisationService.update(id, updateOrganisationDto); + const updatedOrg = await this.organisationService.update(id, updateOrganisationDto); + return { message: 'Product successfully updated', org: updatedOrg }; } catch (error) { throw new HttpException(error.message, error.status || 500); } diff --git a/src/modules/organisation/tests/organisation.service.spec.ts b/src/modules/organisation/tests/organisation.service.spec.ts index 9a42ff7a7..dca006cec 100644 --- a/src/modules/organisation/tests/organisation.service.spec.ts +++ b/src/modules/organisation/tests/organisation.service.spec.ts @@ -58,7 +58,7 @@ describe('OrganisationService', () => { jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(organisation); jest.spyOn(repository, 'update').mockResolvedValueOnce({ affected: 0 } as any); - jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(null); // Trigger BadRequestException + jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(null); await expect(service.update(id, updateOrganisationDto)).rejects.toThrow(BadRequestException); }); diff --git a/src/shared/inteceptors/response.interceptor.ts b/src/shared/inteceptors/response.interceptor.ts index b84edf1ce..61f964796 100644 --- a/src/shared/inteceptors/response.interceptor.ts +++ b/src/shared/inteceptors/response.interceptor.ts @@ -19,7 +19,6 @@ export class ResponseInterceptor implements NestInterceptor { let errorMessage = 'An error occurred'; - // Extract error message from exceptionResponse if (typeof exceptionResponse === 'object' && 'message' in exceptionResponse) { if (Array.isArray(exceptionResponse.message)) { errorMessage = exceptionResponse.message.join(', '); From 1c5711e3caac513b1ff84bd24d0ffbf9f20e5373 Mon Sep 17 00:00:00 2001 From: King-Mikaelson Date: Wed, 24 Jul 2024 01:18:04 +0100 Subject: [PATCH 06/18] feat(email-service): Implement email sending functionalities --- package-lock.json | 2498 ++++++++++++++++- package.json | 4 +- src/app.module.ts | 28 + src/modules/email/article.interface.ts | 5 + src/modules/email/email.controller.spec.ts | 32 + src/modules/email/email.controller.ts | 7 + src/modules/email/email.module.ts | 11 + src/modules/email/email.service.spec.ts | 113 + src/modules/email/email.service.ts | 59 + src/modules/email/templates/confirmation.hbs | 40 + src/modules/email/templates/newsletter.hbs | 43 + .../email/templates/reset-password.hbs | 40 + src/modules/email/templates/waitlist.hbs | 39 + 13 files changed, 2878 insertions(+), 41 deletions(-) create mode 100644 src/modules/email/article.interface.ts create mode 100644 src/modules/email/email.controller.spec.ts create mode 100644 src/modules/email/email.controller.ts create mode 100644 src/modules/email/email.module.ts create mode 100644 src/modules/email/email.service.spec.ts create mode 100644 src/modules/email/email.service.ts create mode 100644 src/modules/email/templates/confirmation.hbs create mode 100644 src/modules/email/templates/newsletter.hbs create mode 100644 src/modules/email/templates/reset-password.hbs create mode 100644 src/modules/email/templates/waitlist.hbs diff --git a/package-lock.json b/package-lock.json index d7d7ea108..af20e84ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "UNLICENSED", "dependencies": { "@faker-js/faker": "^8.4.1", + "@nestjs-modules/mailer": "^2.0.2", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.2.3", "@nestjs/core": "^10.0.0", @@ -23,6 +24,7 @@ "class-validator": "^0.14.1", "joi": "^17.6.0", "nestjs-pino": "^4.1.0", + "nodemailer": "^6.9.14", "passport-jwt": "^4.0.1", "pg": "^8.12.0", "reflect-metadata": "^0.2.2", @@ -455,7 +457,7 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6.9.0" } @@ -464,7 +466,7 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6.9.0" } @@ -581,7 +583,7 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", - "dev": true, + "devOptional": true, "bin": { "parser": "bin/babel-parser.js" }, @@ -766,6 +768,18 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.8.tgz", + "integrity": "sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==", + "optional": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", @@ -814,7 +828,7 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", - "dev": true, + "devOptional": true, "dependencies": { "@babel/helper-string-parser": "^7.24.7", "@babel/helper-validator-identifier": "^7.24.7", @@ -1623,6 +1637,176 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@css-inline/css-inline": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline/-/css-inline-0.14.1.tgz", + "integrity": "sha512-u4eku+hnPqqHIGq/ZUQcaP0TrCbYeLIYBaK7qClNRGZbnh8RC4gVxLEIo8Pceo1nOK9E5G4Lxzlw5KnXcvflfA==", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@css-inline/css-inline-android-arm-eabi": "0.14.1", + "@css-inline/css-inline-android-arm64": "0.14.1", + "@css-inline/css-inline-darwin-arm64": "0.14.1", + "@css-inline/css-inline-darwin-x64": "0.14.1", + "@css-inline/css-inline-linux-arm-gnueabihf": "0.14.1", + "@css-inline/css-inline-linux-arm64-gnu": "0.14.1", + "@css-inline/css-inline-linux-arm64-musl": "0.14.1", + "@css-inline/css-inline-linux-x64-gnu": "0.14.1", + "@css-inline/css-inline-linux-x64-musl": "0.14.1", + "@css-inline/css-inline-win32-x64-msvc": "0.14.1" + } + }, + "node_modules/@css-inline/css-inline-android-arm-eabi": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-android-arm-eabi/-/css-inline-android-arm-eabi-0.14.1.tgz", + "integrity": "sha512-LNUR8TY4ldfYi0mi/d4UNuHJ+3o8yLQH9r2Nt6i4qeg1i7xswfL3n/LDLRXvGjBYqeEYNlhlBQzbPwMX1qrU6A==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@css-inline/css-inline-android-arm64": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-android-arm64/-/css-inline-android-arm64-0.14.1.tgz", + "integrity": "sha512-tH5us0NYGoTNBHOUHVV7j9KfJ4DtFOeTLA3cM0XNoMtArNu2pmaaBMFJPqECzavfXkLc7x5Z22UPZYjoyHfvCA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@css-inline/css-inline-darwin-arm64": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-darwin-arm64/-/css-inline-darwin-arm64-0.14.1.tgz", + "integrity": "sha512-QE5W1YRIfRayFrtrcK/wqEaxNaqLULPI0gZB4ArbFRd3d56IycvgBasDTHPre5qL2cXCO3VyPx+80XyHOaVkag==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@css-inline/css-inline-darwin-x64": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-darwin-x64/-/css-inline-darwin-x64-0.14.1.tgz", + "integrity": "sha512-mAvv2sN8awNFsbvBzlFkZPbCNZ6GCWY5/YcIz7V5dPYw+bHHRbjnlkNTEZq5BsDxErVrMIGvz05PGgzuNvZvdQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@css-inline/css-inline-linux-arm-gnueabihf": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-linux-arm-gnueabihf/-/css-inline-linux-arm-gnueabihf-0.14.1.tgz", + "integrity": "sha512-AWC44xL0X7BgKvrWEqfSqkT2tJA5kwSGrAGT+m0gt11wnTYySvQ6YpX0fTY9i3ppYGu4bEdXFjyK2uY1DTQMHA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@css-inline/css-inline-linux-arm64-gnu": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-linux-arm64-gnu/-/css-inline-linux-arm64-gnu-0.14.1.tgz", + "integrity": "sha512-drj0ciiJgdP3xKXvNAt4W+FH4KKMs8vB5iKLJ3HcH07sNZj58Sx++2GxFRS1el3p+GFp9OoYA6dgouJsGEqt0Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@css-inline/css-inline-linux-arm64-musl": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-linux-arm64-musl/-/css-inline-linux-arm64-musl-0.14.1.tgz", + "integrity": "sha512-FzknI+st8eA8YQSdEJU9ykcM0LZjjigBuynVF5/p7hiMm9OMP8aNhWbhZ8LKJpKbZrQsxSGS4g9Vnr6n6FiSdQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@css-inline/css-inline-linux-x64-gnu": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-linux-x64-gnu/-/css-inline-linux-x64-gnu-0.14.1.tgz", + "integrity": "sha512-yubbEye+daDY/4vXnyASAxH88s256pPati1DfVoZpU1V0+KP0BZ1dByZOU1ktExurbPH3gZOWisAnBE9xon0Uw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@css-inline/css-inline-linux-x64-musl": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-linux-x64-musl/-/css-inline-linux-x64-musl-0.14.1.tgz", + "integrity": "sha512-6CRAZzoy1dMLPC/tns2rTt1ZwPo0nL/jYBEIAsYTCWhfAnNnpoLKVh5Nm+fSU3OOwTTqU87UkGrFJhObD/wobQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@css-inline/css-inline-win32-x64-msvc": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/@css-inline/css-inline-win32-x64-msvc/-/css-inline-win32-x64-msvc-0.14.1.tgz", + "integrity": "sha512-nzotGiaiuiQW78EzsiwsHZXbxEt6DiMUFcDJ6dhiliomXxnlaPyBfZb6/FMBgRJOf6sknDt/5695OttNmbMYzg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -2503,6 +2687,61 @@ "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz", "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==" }, + "node_modules/@nestjs-modules/mailer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@nestjs-modules/mailer/-/mailer-2.0.2.tgz", + "integrity": "sha512-+z4mADQasg0H1ZaGu4zZTuKv2pu+XdErqx99PLFPzCDNTN/q9U59WPgkxVaHnsvKHNopLj5Xap7G4ZpptduoYw==", + "dependencies": { + "@css-inline/css-inline": "0.14.1", + "glob": "10.3.12" + }, + "optionalDependencies": { + "@types/ejs": "^3.1.5", + "@types/mjml": "^4.7.4", + "@types/pug": "^2.0.10", + "ejs": "^3.1.10", + "handlebars": "^4.7.8", + "liquidjs": "^10.11.1", + "mjml": "^4.15.3", + "preview-email": "^3.0.19", + "pug": "^3.0.2" + }, + "peerDependencies": { + "@nestjs/common": ">=7.0.9", + "@nestjs/core": ">=7.0.9", + "@types/ejs": ">=3.0.3", + "@types/mjml": ">=4.7.4", + "@types/pug": ">=2.0.6", + "ejs": ">=3.1.2", + "handlebars": ">=4.7.6", + "liquidjs": ">=10.8.2", + "mjml": ">=4.15.3", + "nodemailer": ">=6.4.6", + "preview-email": ">=3.0.19", + "pug": ">=3.0.1" + } + }, + "node_modules/@nestjs-modules/mailer/node_modules/glob": { + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@nestjs/cli": { "version": "10.3.2", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.3.2.tgz", @@ -2917,6 +3156,12 @@ "npm": ">=5.0.0" } }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "optional": true + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -2938,6 +3183,19 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/@selderee/plugin-htmlparser2": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz", + "integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==", + "optional": true, + "dependencies": { + "domhandler": "^5.0.3", + "selderee": "^0.11.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -3093,6 +3351,12 @@ "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", "dev": true }, + "node_modules/@types/ejs": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.5.tgz", + "integrity": "sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==", + "optional": true + }, "node_modules/@types/eslint": { "version": "8.56.10", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", @@ -3224,6 +3488,21 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true }, + "node_modules/@types/mjml": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/@types/mjml/-/mjml-4.7.4.tgz", + "integrity": "sha512-vyi1vzWgMzFMwZY7GSZYX0GU0dmtC8vLHwpgk+NWmwbwRSrlieVyJ9sn5elodwUfklJM7yGl0zQeet1brKTWaQ==", + "optional": true, + "dependencies": { + "@types/mjml-core": "*" + } + }, + "node_modules/@types/mjml-core": { + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@types/mjml-core/-/mjml-core-4.15.0.tgz", + "integrity": "sha512-jSRWTOpwRS/uHIBfGdvLl0a7MaoBZZYHKI+HhsFYChrUOKVJTnjSYsuV6wx0snv6ZaX3TUo5OP/gNsz/uzZz1A==", + "optional": true + }, "node_modules/@types/node": { "version": "20.14.2", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", @@ -3261,6 +3540,12 @@ "@types/passport": "*" } }, + "node_modules/@types/pug": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz", + "integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==", + "optional": true + }, "node_modules/@types/qs": { "version": "6.9.15", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", @@ -3825,11 +4110,46 @@ } } }, + "node_modules/alce": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/alce/-/alce-1.2.0.tgz", + "integrity": "sha512-XppPf2S42nO2WhvKzlwzlfcApcXHzjlod30pKmcWjRgLOtqoe5DMuqdiYoM6AgyXksc6A6pV4v1L/WW217e57w==", + "optional": true, + "dependencies": { + "esprima": "^1.2.0", + "estraverse": "^1.5.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/alce/node_modules/esprima": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.5.tgz", + "integrity": "sha512-S9VbPDU0adFErpDai3qDkjq8+G05ONtKzcyNrPKg/ZKa+tf879nX2KexNU95b31UoTJjRLInNBHHHjFPoCd7lQ==", + "optional": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/alce/node_modules/estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "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, + "devOptional": true, "engines": { "node": ">=6" } @@ -3892,7 +4212,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, + "devOptional": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -3905,7 +4225,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8.6" }, @@ -4108,6 +4428,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "optional": true + }, + "node_modules/assert-never": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/assert-never/-/assert-never-1.3.0.tgz", + "integrity": "sha512-9Z3vxQ+berkL/JJo0dK+EY3Lp0s3NtSnP3VCLsh5HDcZPrh0M+KQRK5sWhUeyPPH+/RCxZqOxLMR+YC6vlviEQ==", + "optional": true + }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "optional": true + }, "node_modules/atomic-sleep": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", @@ -4248,6 +4586,18 @@ "@babel/core": "^7.0.0" } }, + "node_modules/babel-walk": { + "version": "3.0.0-canary-5", + "resolved": "https://registry.npmjs.org/babel-walk/-/babel-walk-3.0.0-canary-5.tgz", + "integrity": "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==", + "optional": true, + "dependencies": { + "@babel/types": "^7.9.6" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -4289,7 +4639,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" }, @@ -4358,6 +4708,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "optional": true + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -4510,6 +4866,31 @@ "node": ">=6" } }, + "node_modules/camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==", + "optional": true, + "dependencies": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "node_modules/camel-case/node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "optional": true + }, + "node_modules/camel-case/node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "optional": true, + "dependencies": { + "lower-case": "^1.1.1" + } + }, "node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -4563,17 +4944,89 @@ "node": ">=10" } }, + "node_modules/character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", + "optional": true, + "dependencies": { + "is-regex": "^1.0.3" + } + }, "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 }, + "node_modules/cheerio": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", + "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==", + "optional": true, + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "htmlparser2": "^8.0.1", + "parse5": "^7.0.0", + "parse5-htmlparser2-tree-adapter": "^7.0.0" + }, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "optional": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cheerio/node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "optional": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/cheerio/node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "optional": true, + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "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, + "devOptional": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -4614,7 +5067,7 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -4646,9 +5099,30 @@ "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", + "node_modules/clean-css": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", + "optional": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/clean-css/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==", + "optional": true, + "engines": { + "node": ">=0.10.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, "dependencies": { @@ -4969,6 +5443,22 @@ "typedarray": "^0.0.6" } }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "optional": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "optional": true + }, "node_modules/consola": { "version": "2.15.3", "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", @@ -4979,6 +5469,16 @@ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, + "node_modules/constantinople": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-4.0.1.tgz", + "integrity": "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==", + "optional": true, + "dependencies": { + "@babel/parser": "^7.6.0", + "@babel/types": "^7.6.1" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -5159,6 +5659,34 @@ "node": ">= 8" } }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "optional": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "optional": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, "node_modules/dargs": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", @@ -5257,6 +5785,15 @@ } } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -5267,7 +5804,7 @@ "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, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -5344,6 +5881,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -5356,11 +5902,17 @@ "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, + "devOptional": true, "engines": { "node": ">=8" } }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "optional": true + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -5391,6 +5943,19 @@ "node": ">=8" } }, + "node_modules/display-notification": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/display-notification/-/display-notification-2.0.0.tgz", + "integrity": "sha512-TdmtlAcdqy1NU+j7zlkDdMnCL878zriLaBmoD9quOoq1ySSSGv03l0hXK5CvIFZlIfFI/hizqdQuW+Num7xuhw==", + "optional": true, + "dependencies": { + "escape-string-applescript": "^1.0.0", + "run-applescript": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -5403,6 +5968,67 @@ "node": ">=6.0.0" } }, + "node_modules/doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==", + "optional": true + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "optional": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "optional": true + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "optional": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "optional": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -5461,11 +6087,68 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "optional": true, + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "optional": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "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==" }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "optional": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.796", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.796.tgz", @@ -5497,6 +6180,15 @@ "node": ">= 0.8" } }, + "node_modules/encoding-japanese": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.1.0.tgz", + "integrity": "sha512-58XySVxUgVlBikBTbQ8WdDxBDHIdXucB16LO5PBHR8t75D54wQrNo4cg+58+R1CtJfKnsVsvt9XlteRaR8xw1w==", + "optional": true, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.17.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", @@ -5510,6 +6202,18 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "optional": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -5684,11 +6388,32 @@ "node": ">=6" } }, + "node_modules/escape-goat": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-3.0.0.tgz", + "integrity": "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "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==" }, + "node_modules/escape-string-applescript": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/escape-string-applescript/-/escape-string-applescript-1.0.0.tgz", + "integrity": "sha512-4/hFwoYaC6TkpDn9A3pTC52zQPArFeXuIfhUtCGYdauTzXVP9H3BDr3oO/QzQehMpLDC7srvYgfwvImPFGfvBA==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -6260,6 +6985,12 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/extend-object": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/extend-object/-/extend-object-1.0.0.tgz", + "integrity": "sha512-0dHDIXC7y7LDmCh/lp1oYkmv73K25AMugQI07r8eFopkW6f7Ufn1q+ETMsJjnV9Am14SlElkqy3O92r6xEaxPw==", + "optional": true + }, "node_modules/external-editor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", @@ -6380,6 +7111,27 @@ "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==", + "optional": true, + "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==", + "optional": true, + "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", @@ -6437,6 +7189,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/fixpack": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fixpack/-/fixpack-4.0.0.tgz", + "integrity": "sha512-5SM1+H2CcuJ3gGEwTiVo/+nd/hYpNj9Ch3iMDOQ58ndY+VGQ2QdvaUTkd3otjZvYnd/8LF/HkJ5cx7PBq0orCQ==", + "optional": true, + "dependencies": { + "alce": "1.2.0", + "chalk": "^3.0.0", + "detect-indent": "^6.0.0", + "detect-newline": "^3.1.0", + "extend-object": "^1.0.0", + "rc": "^1.2.8" + }, + "bin": { + "fixpack": "bin/fixpack" + } + }, + "node_modules/fixpack/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "optional": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", @@ -6796,6 +7578,18 @@ "node": ">=8.0.0" } }, + "node_modules/get-port": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", + "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -6969,6 +7763,36 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "optional": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars/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==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has-bigints": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", @@ -7032,7 +7856,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, + "devOptional": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -7059,6 +7883,15 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "optional": true, + "bin": { + "he": "bin/he" + } + }, "node_modules/highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -7073,23 +7906,85 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "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==", + "node_modules/html-minifier": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz", + "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==", + "optional": true, "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "camel-case": "^3.0.0", + "clean-css": "^4.2.1", + "commander": "^2.19.0", + "he": "^1.2.0", + "param-case": "^2.1.1", + "relateurl": "^0.2.7", + "uglify-js": "^3.5.1" + }, + "bin": { + "html-minifier": "cli.js" }, "engines": { - "node": ">= 0.8" + "node": ">=6" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", + "node_modules/html-minifier/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==", + "optional": true + }, + "node_modules/html-to-text": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz", + "integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==", + "optional": true, + "dependencies": { + "@selderee/plugin-htmlparser2": "^0.11.0", + "deepmerge": "^4.3.1", + "dom-serializer": "^2.0.0", + "htmlparser2": "^8.0.2", + "selderee": "^0.11.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "optional": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "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==", + "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/https-proxy-agent": { + "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dependencies": { @@ -7336,7 +8231,7 @@ "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, + "devOptional": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -7376,7 +8271,7 @@ "version": "2.13.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", - "dev": true, + "devOptional": true, "dependencies": { "hasown": "^2.0.0" }, @@ -7414,6 +8309,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "optional": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "optional": true, + "dependencies": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, + "node_modules/is-expression/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "optional": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -7512,11 +8444,17 @@ "node": ">=8" } }, + "node_modules/is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", + "optional": true + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, + "devOptional": true, "dependencies": { "call-bind": "^1.0.2", "has-tostringtag": "^1.0.0" @@ -7636,6 +8574,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "optional": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -7746,6 +8696,46 @@ "@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==", + "optional": true, + "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==", + "optional": true, + "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==", + "optional": true, + "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", @@ -8438,6 +9428,66 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/js-beautify": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", + "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==", + "optional": true, + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.4", + "glob": "^10.3.3", + "js-cookie": "^3.0.5", + "nopt": "^7.2.0" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-beautify/node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "optional": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/js-beautify/node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "optional": true, + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==", + "optional": true + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8567,6 +9617,44 @@ "npm": ">=6" } }, + "node_modules/jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", + "optional": true, + "dependencies": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, + "node_modules/juice": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/juice/-/juice-10.0.0.tgz", + "integrity": "sha512-9f68xmhGrnIi6DBkiiP3rUrQN33SEuaKu1+njX6VgMP+jwZAsnT33WIzlrWICL9matkhYu3OyrqSUP55YTIdGg==", + "optional": true, + "dependencies": { + "cheerio": "^1.0.0-rc.12", + "commander": "^6.1.0", + "mensch": "^0.3.4", + "slick": "^1.12.2", + "web-resource-inliner": "^6.0.1" + }, + "bin": { + "juice": "bin/juice" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/juice/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "optional": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -8604,6 +9692,15 @@ "node": ">=6" } }, + "node_modules/leac": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz", + "integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==", + "optional": true, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -8626,11 +9723,47 @@ "node": ">= 0.8.0" } }, + "node_modules/libbase64": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-1.3.0.tgz", + "integrity": "sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==", + "optional": true + }, + "node_modules/libmime": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.3.5.tgz", + "integrity": "sha512-nSlR1yRZ43L3cZCiWEw7ali3jY29Hz9CQQ96Oy+sSspYnIP5N54ucOPHqooBsXzwrX1pwn13VUE05q4WmzfaLg==", + "optional": true, + "dependencies": { + "encoding-japanese": "2.1.0", + "iconv-lite": "0.6.3", + "libbase64": "1.3.0", + "libqp": "2.1.0" + } + }, + "node_modules/libmime/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==", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/libphonenumber-js": { "version": "1.11.3", "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.3.tgz", "integrity": "sha512-RU0CTsLCu2v6VEzdP+W6UU2n5+jEpMDRkGxUeBgsAJgre3vKgm17eApISH9OQY4G0jZYJVIc8qXmz6CJFueAFg==" }, + "node_modules/libqp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/libqp/-/libqp-2.1.0.tgz", + "integrity": "sha512-O6O6/fsG5jiUVbvdgT7YX3xY3uIadR6wEZ7+vy9u7PKHAlSEB6blvC1o5pHBjgsi95Uo0aiBBdkyFecj6jtb7A==", + "optional": true + }, "node_modules/lilconfig": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", @@ -8649,6 +9782,15 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "optional": true, + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/lint-staged": { "version": "15.2.5", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.5.tgz", @@ -8819,6 +9961,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/liquidjs": { + "version": "10.16.0", + "resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-10.16.0.tgz", + "integrity": "sha512-XIgkYmiEXt1dS6Pi3IMIed43mMf9IuejnmmRiIo9g56GsKtYvPW5Y1AcM3cN1yyMsl0H5PfRoj4Y5DUpbNmg9g==", + "optional": true, + "dependencies": { + "commander": "^10.0.0" + }, + "bin": { + "liquid": "bin/liquid.js", + "liquidjs": "bin/liquid.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/liquidjs" + } + }, + "node_modules/liquidjs/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/listr2": { "version": "8.2.1", "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.1.tgz", @@ -9269,6 +10440,101 @@ "node": ">=12" } }, + "node_modules/mailparser": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.7.1.tgz", + "integrity": "sha512-RCnBhy5q8XtB3mXzxcAfT1huNqN93HTYYyL6XawlIKycfxM/rXPg9tXoZ7D46+SgCS1zxKzw+BayDQSvncSTTw==", + "optional": true, + "dependencies": { + "encoding-japanese": "2.1.0", + "he": "1.2.0", + "html-to-text": "9.0.5", + "iconv-lite": "0.6.3", + "libmime": "5.3.5", + "linkify-it": "5.0.0", + "mailsplit": "5.4.0", + "nodemailer": "6.9.13", + "punycode.js": "2.3.1", + "tlds": "1.252.0" + } + }, + "node_modules/mailparser/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==", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mailparser/node_modules/nodemailer": { + "version": "6.9.13", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.13.tgz", + "integrity": "sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA==", + "optional": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/mailsplit": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/mailsplit/-/mailsplit-5.4.0.tgz", + "integrity": "sha512-wnYxX5D5qymGIPYLwnp6h8n1+6P6vz/MJn5AzGjZ8pwICWssL+CCQjWBIToOVHASmATot4ktvlLo6CyLfOXWYA==", + "optional": true, + "dependencies": { + "libbase64": "1.2.1", + "libmime": "5.2.0", + "libqp": "2.0.1" + } + }, + "node_modules/mailsplit/node_modules/encoding-japanese": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.0.0.tgz", + "integrity": "sha512-++P0RhebUC8MJAwJOsT93dT+5oc5oPImp1HubZpAuCZ5kTLnhuuBhKHj2jJeO/Gj93idPBWmIuQ9QWMe5rX3pQ==", + "optional": true, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/mailsplit/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==", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mailsplit/node_modules/libbase64": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/libbase64/-/libbase64-1.2.1.tgz", + "integrity": "sha512-l+nePcPbIG1fNlqMzrh68MLkX/gTxk/+vdvAb388Ssi7UuUN31MI44w4Yf33mM3Cm4xDfw48mdf3rkdHszLNew==", + "optional": true + }, + "node_modules/mailsplit/node_modules/libmime": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/libmime/-/libmime-5.2.0.tgz", + "integrity": "sha512-X2U5Wx0YmK0rXFbk67ASMeqYIkZ6E5vY7pNWRKtnNzqjvdYYG8xtPDpCnuUEnPU9vlgNev+JoSrcaKSUaNvfsw==", + "optional": true, + "dependencies": { + "encoding-japanese": "2.0.0", + "iconv-lite": "0.6.3", + "libbase64": "1.2.1", + "libqp": "2.0.1" + } + }, + "node_modules/mailsplit/node_modules/libqp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/libqp/-/libqp-2.0.1.tgz", + "integrity": "sha512-Ka0eC5LkF3IPNQHJmYBWljJsw0UvM6j+QdKRbWyCdTmYwvIDE6a7bCm0UkTAL/K+3KXK5qXT/ClcInU01OpdLg==", + "optional": true + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -9319,7 +10585,13 @@ "node": ">= 4.0.0" } }, - "node_modules/meow": { + "node_modules/mensch": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/mensch/-/mensch-0.3.4.tgz", + "integrity": "sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==", + "optional": true + }, + "node_modules/meow": { "version": "12.1.1", "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", @@ -9478,6 +10750,431 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/mjml": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml/-/mjml-4.15.3.tgz", + "integrity": "sha512-bW2WpJxm6HS+S3Yu6tq1DUPFoTxU9sPviUSmnL7Ua+oVO3WA5ILFWqvujUlz+oeuM+HCwEyMiP5xvKNPENVjYA==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "mjml-cli": "4.15.3", + "mjml-core": "4.15.3", + "mjml-migrate": "4.15.3", + "mjml-preset-core": "4.15.3", + "mjml-validator": "4.15.3" + }, + "bin": { + "mjml": "bin/mjml" + } + }, + "node_modules/mjml-accordion": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-accordion/-/mjml-accordion-4.15.3.tgz", + "integrity": "sha512-LPNVSj1LyUVYT9G1gWwSw3GSuDzDsQCu0tPB2uDsq4VesYNnU6v3iLCQidMiR6azmIt13OEozG700ygAUuA6Ng==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-body": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-body/-/mjml-body-4.15.3.tgz", + "integrity": "sha512-7pfUOVPtmb0wC+oUOn4xBsAw4eT5DyD6xqaxj/kssu6RrFXOXgJaVnDPAI9AzIvXJ/5as9QrqRGYAddehwWpHQ==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-button": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-button/-/mjml-button-4.15.3.tgz", + "integrity": "sha512-79qwn9AgdGjJR1vLnrcm2rq2AsAZkKC5JPwffTMG+Nja6zGYpTDZFZ56ekHWr/r1b5WxkukcPj2PdevUug8c+Q==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-carousel": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-carousel/-/mjml-carousel-4.15.3.tgz", + "integrity": "sha512-3ju6I4l7uUhPRrJfN3yK9AMsfHvrYbRkcJ1GRphFHzUj37B2J6qJOQUpzA547Y4aeh69TSb7HFVf1t12ejQxVw==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-cli": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-cli/-/mjml-cli-4.15.3.tgz", + "integrity": "sha512-+V2TDw3tXUVEptFvLSerz125C2ogYl8klIBRY1m5BHd4JvGVf3yhx8N3PngByCzA6PGcv/eydGQN+wy34SHf0Q==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "chokidar": "^3.0.0", + "glob": "^10.3.10", + "html-minifier": "^4.0.0", + "js-beautify": "^1.6.14", + "lodash": "^4.17.21", + "minimatch": "^9.0.3", + "mjml-core": "4.15.3", + "mjml-migrate": "4.15.3", + "mjml-parser-xml": "4.15.3", + "mjml-validator": "4.15.3", + "yargs": "^17.7.2" + }, + "bin": { + "mjml-cli": "bin/mjml" + } + }, + "node_modules/mjml-column": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-column/-/mjml-column-4.15.3.tgz", + "integrity": "sha512-hYdEFdJGHPbZJSEysykrevEbB07yhJGSwfDZEYDSbhQQFjV2tXrEgYcFD5EneMaowjb55e3divSJxU4c5q4Qgw==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-core": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-core/-/mjml-core-4.15.3.tgz", + "integrity": "sha512-Dmwk+2cgSD9L9GmTbEUNd8QxkTZtW9P7FN/ROZW/fGZD6Hq6/4TB0zEspg2Ow9eYjZXO2ofOJ3PaQEEShKV0kQ==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "cheerio": "1.0.0-rc.12", + "detect-node": "^2.0.4", + "html-minifier": "^4.0.0", + "js-beautify": "^1.6.14", + "juice": "^10.0.0", + "lodash": "^4.17.21", + "mjml-migrate": "4.15.3", + "mjml-parser-xml": "4.15.3", + "mjml-validator": "4.15.3" + } + }, + "node_modules/mjml-divider": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-divider/-/mjml-divider-4.15.3.tgz", + "integrity": "sha512-vh27LQ9FG/01y0b9ntfqm+GT5AjJnDSDY9hilss2ixIUh0FemvfGRfsGVeV5UBVPBKK7Ffhvfqc7Rciob9Spzw==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-group": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-group/-/mjml-group-4.15.3.tgz", + "integrity": "sha512-HSu/rKnGZVKFq3ciT46vi1EOy+9mkB0HewO4+P6dP/Y0UerWkN6S3UK11Cxsj0cAp0vFwkPDCdOeEzRdpFEkzA==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-head": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head/-/mjml-head-4.15.3.tgz", + "integrity": "sha512-o3mRuuP/MB5fZycjD3KH/uXsnaPl7Oo8GtdbJTKtH1+O/3pz8GzGMkscTKa97l03DAG2EhGrzzLcU2A6eshwFw==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-head-attributes": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-attributes/-/mjml-head-attributes-4.15.3.tgz", + "integrity": "sha512-2ISo0r5ZKwkrvJgDou9xVPxxtXMaETe2AsAA02L89LnbB2KC0N5myNsHV0sEysTw9+CfCmgjAb0GAI5QGpxKkQ==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-head-breakpoint": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-breakpoint/-/mjml-head-breakpoint-4.15.3.tgz", + "integrity": "sha512-Eo56FA5C2v6ucmWQL/JBJ2z641pLOom4k0wP6CMZI2utfyiJ+e2Uuinj1KTrgDcEvW4EtU9HrfAqLK9UosLZlg==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-head-font": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-font/-/mjml-head-font-4.15.3.tgz", + "integrity": "sha512-CzV2aDPpiNIIgGPHNcBhgyedKY4SX3BJoTwOobSwZVIlEA6TAWB4Z9WwFUmQqZOgo1AkkiTHPZQvGcEhFFXH6g==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-head-html-attributes": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-html-attributes/-/mjml-head-html-attributes-4.15.3.tgz", + "integrity": "sha512-MDNDPMBOgXUZYdxhosyrA2kudiGO8aogT0/cODyi2Ed9o/1S7W+je11JUYskQbncqhWKGxNyaP4VWa+6+vUC/g==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-head-preview": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-preview/-/mjml-head-preview-4.15.3.tgz", + "integrity": "sha512-J2PxCefUVeFwsAExhrKo4lwxDevc5aKj888HBl/wN4EuWOoOg06iOGCxz4Omd8dqyFsrqvbBuPqRzQ+VycGmaA==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-head-style": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-style/-/mjml-head-style-4.15.3.tgz", + "integrity": "sha512-9J+JuH+mKrQU65CaJ4KZegACUgNIlYmWQYx3VOBR/tyz+8kDYX7xBhKJCjQ1I4wj2Tvga3bykd89Oc2kFZ5WOw==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-head-title": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-head-title/-/mjml-head-title-4.15.3.tgz", + "integrity": "sha512-IM59xRtsxID4DubQ0iLmoCGXguEe+9BFG4z6y2xQDrscIa4QY3KlfqgKGT69ojW+AVbXXJPEVqrAi4/eCsLItQ==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-hero": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-hero/-/mjml-hero-4.15.3.tgz", + "integrity": "sha512-9cLAPuc69yiuzNrMZIN58j+HMK1UWPaq2i3/Fg2ZpimfcGFKRcPGCbEVh0v+Pb6/J0+kf8yIO0leH20opu3AyQ==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-image": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-image/-/mjml-image-4.15.3.tgz", + "integrity": "sha512-g1OhSdofIytE9qaOGdTPmRIp7JsCtgO0zbsn1Fk6wQh2gEL55Z40j/VoghslWAWTgT2OHFdBKnMvWtN6U5+d2Q==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-migrate": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-migrate/-/mjml-migrate-4.15.3.tgz", + "integrity": "sha512-sr/+35RdxZroNQVegjpfRHJ5hda9XCgaS4mK2FGO+Mb1IUevKfeEPII3F/cHDpNwFeYH3kAgyqQ22ClhGLWNBA==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "js-beautify": "^1.6.14", + "lodash": "^4.17.21", + "mjml-core": "4.15.3", + "mjml-parser-xml": "4.15.3", + "yargs": "^17.7.2" + }, + "bin": { + "migrate": "lib/cli.js" + } + }, + "node_modules/mjml-navbar": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-navbar/-/mjml-navbar-4.15.3.tgz", + "integrity": "sha512-VsKH/Jdlf8Yu3y7GpzQV5n7JMdpqvZvTSpF6UQXL0PWOm7k6+LX+sCZimOfpHJ+wCaaybpxokjWZ71mxOoCWoA==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-parser-xml": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-parser-xml/-/mjml-parser-xml-4.15.3.tgz", + "integrity": "sha512-Tz0UX8/JVYICLjT+U8J1f/TFxIYVYjzZHeh4/Oyta0pLpRLeZlxEd71f3u3kdnulCKMP4i37pFRDmyLXAlEuLw==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "detect-node": "2.1.0", + "htmlparser2": "^9.1.0", + "lodash": "^4.17.15" + } + }, + "node_modules/mjml-parser-xml/node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "optional": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, + "node_modules/mjml-preset-core": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-preset-core/-/mjml-preset-core-4.15.3.tgz", + "integrity": "sha512-1zZS8P4O0KweWUqNS655+oNnVMPQ1Rq1GaZq5S9JfwT1Vh/m516lSmiTW9oko6gGHytt5s6Yj6oOeu5Zm8FoLw==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "mjml-accordion": "4.15.3", + "mjml-body": "4.15.3", + "mjml-button": "4.15.3", + "mjml-carousel": "4.15.3", + "mjml-column": "4.15.3", + "mjml-divider": "4.15.3", + "mjml-group": "4.15.3", + "mjml-head": "4.15.3", + "mjml-head-attributes": "4.15.3", + "mjml-head-breakpoint": "4.15.3", + "mjml-head-font": "4.15.3", + "mjml-head-html-attributes": "4.15.3", + "mjml-head-preview": "4.15.3", + "mjml-head-style": "4.15.3", + "mjml-head-title": "4.15.3", + "mjml-hero": "4.15.3", + "mjml-image": "4.15.3", + "mjml-navbar": "4.15.3", + "mjml-raw": "4.15.3", + "mjml-section": "4.15.3", + "mjml-social": "4.15.3", + "mjml-spacer": "4.15.3", + "mjml-table": "4.15.3", + "mjml-text": "4.15.3", + "mjml-wrapper": "4.15.3" + } + }, + "node_modules/mjml-raw": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-raw/-/mjml-raw-4.15.3.tgz", + "integrity": "sha512-IGyHheOYyRchBLiAEgw3UM11kFNmBSMupu2BDdejC6ZiDhEAdG+tyERlsCwDPYtXanvFpGWULIu3XlsUPc+RZw==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-section": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-section/-/mjml-section-4.15.3.tgz", + "integrity": "sha512-JfVPRXH++Hd933gmQfG8JXXCBCR6fIzC3DwiYycvanL/aW1cEQ2EnebUfQkt5QzlYjOkJEH+JpccAsq3ln6FZQ==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-social": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-social/-/mjml-social-4.15.3.tgz", + "integrity": "sha512-7sD5FXrESOxpT9Z4Oh36bS6u/geuUrMP1aCg2sjyAwbPcF1aWa2k9OcatQfpRf6pJEhUZ18y6/WBBXmMVmSzXg==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-spacer": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-spacer/-/mjml-spacer-4.15.3.tgz", + "integrity": "sha512-3B7Qj+17EgDdAtZ3NAdMyOwLTX1jfmJuY7gjyhS2HtcZAmppW+cxqHUBwCKfvSRgTQiccmEvtNxaQK+tfyrZqA==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-table": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-table/-/mjml-table-4.15.3.tgz", + "integrity": "sha512-FLx7DcRKTdKdcOCbMyBaeudeHaHpwPveRrBm6WyQe3LXx6FfdmOh59i71/16LFQMgBOD3N4/UJkzxLzlTJzMqQ==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-text": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-text/-/mjml-text-4.15.3.tgz", + "integrity": "sha512-+C0hxCmw9kg0XzT6vhE5mFkK6y225nC8UEQcN94K0fBCjPKkM+HqZMwGX205fzdGRi+Bxa55b/VhrIVwdv+8vw==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3" + } + }, + "node_modules/mjml-validator": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-validator/-/mjml-validator-4.15.3.tgz", + "integrity": "sha512-Xb72KdqRwjv/qM2rJpV22syyP2N3cRQ9VVDrN6u2FSzLq02buFNxmSPJ7CKhat3PrUNdVHU75KZwOf/tz4UEhA==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9" + } + }, + "node_modules/mjml-wrapper": { + "version": "4.15.3", + "resolved": "https://registry.npmjs.org/mjml-wrapper/-/mjml-wrapper-4.15.3.tgz", + "integrity": "sha512-ditsCijeHJrmBmObtJmQ18ddLxv5oPyMTdPU8Di8APOnD2zPk7Z4UAuJSl7HXB45oFiivr3MJf4koFzMUSZ6Gg==", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "lodash": "^4.17.21", + "mjml-core": "4.15.3", + "mjml-section": "4.15.3" + } + }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", @@ -9545,7 +11242,7 @@ "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 + "devOptional": true }, "node_modules/nestjs-pino": { "version": "4.1.0", @@ -9560,6 +11257,12 @@ "pino-http": "^6.4.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" } }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "optional": true + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -9620,6 +11323,14 @@ "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", "dev": true }, + "node_modules/nodemailer": { + "version": "6.9.14", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.14.tgz", + "integrity": "sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -9638,7 +11349,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -9667,6 +11378,18 @@ "set-blocking": "^2.0.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "optional": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -9802,6 +11525,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "optional": true, + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "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", @@ -9851,6 +11590,30 @@ "node": ">=0.10.0" } }, + "node_modules/p-event": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", + "integrity": "sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==", + "optional": true, + "dependencies": { + "p-timeout": "^3.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "optional": true, + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -9881,6 +11644,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "optional": true, + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -9890,6 +11665,45 @@ "node": ">=6" } }, + "node_modules/p-wait-for": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-wait-for/-/p-wait-for-3.2.0.tgz", + "integrity": "sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA==", + "optional": true, + "dependencies": { + "p-timeout": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==", + "optional": true, + "dependencies": { + "no-case": "^2.2.0" + } + }, + "node_modules/param-case/node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==", + "optional": true + }, + "node_modules/param-case/node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "optional": true, + "dependencies": { + "lower-case": "^1.1.1" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -9938,6 +11752,19 @@ "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" }, + "node_modules/parseley": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz", + "integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==", + "optional": true, + "dependencies": { + "leac": "^0.6.0", + "peberminta": "^0.9.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -10019,7 +11846,7 @@ "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 + "devOptional": true }, "node_modules/path-scurry": { "version": "1.11.1", @@ -10064,6 +11891,15 @@ "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==", "peer": true }, + "node_modules/peberminta": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz", + "integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==", + "optional": true, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/pg": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/pg/-/pg-8.12.0.tgz", @@ -10462,6 +12298,28 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/preview-email": { + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/preview-email/-/preview-email-3.0.20.tgz", + "integrity": "sha512-QbAokW2F3p0thQfp2WTZ0rBy+IZuCnf9gIUCLffr+8hq85esq6pzCA7S0eUdD6oTmtKROqoNeH2rXZWrRow7EA==", + "optional": true, + "dependencies": { + "ci-info": "^3.8.0", + "display-notification": "2.0.0", + "fixpack": "^4.0.0", + "get-port": "5.1.1", + "mailparser": "^3.7.1", + "nodemailer": "^6.9.13", + "open": "7", + "p-event": "4.2.0", + "p-wait-for": "3.2.0", + "pug": "^3.0.3", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -10482,6 +12340,15 @@ "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", "peer": true }, + "node_modules/promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "optional": true, + "dependencies": { + "asap": "~2.0.3" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -10495,6 +12362,12 @@ "node": ">= 6" } }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "optional": true + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -10507,6 +12380,130 @@ "node": ">= 0.10" } }, + "node_modules/pug": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pug/-/pug-3.0.3.tgz", + "integrity": "sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==", + "optional": true, + "dependencies": { + "pug-code-gen": "^3.0.3", + "pug-filters": "^4.0.0", + "pug-lexer": "^5.0.1", + "pug-linker": "^4.0.0", + "pug-load": "^3.0.0", + "pug-parser": "^6.0.0", + "pug-runtime": "^3.0.1", + "pug-strip-comments": "^2.0.0" + } + }, + "node_modules/pug-attrs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-3.0.0.tgz", + "integrity": "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==", + "optional": true, + "dependencies": { + "constantinople": "^4.0.1", + "js-stringify": "^1.0.2", + "pug-runtime": "^3.0.0" + } + }, + "node_modules/pug-code-gen": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-3.0.3.tgz", + "integrity": "sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==", + "optional": true, + "dependencies": { + "constantinople": "^4.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.2", + "pug-attrs": "^3.0.0", + "pug-error": "^2.1.0", + "pug-runtime": "^3.0.1", + "void-elements": "^3.1.0", + "with": "^7.0.0" + } + }, + "node_modules/pug-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.1.0.tgz", + "integrity": "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==", + "optional": true + }, + "node_modules/pug-filters": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-4.0.0.tgz", + "integrity": "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==", + "optional": true, + "dependencies": { + "constantinople": "^4.0.1", + "jstransformer": "1.0.0", + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0", + "resolve": "^1.15.1" + } + }, + "node_modules/pug-lexer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", + "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", + "optional": true, + "dependencies": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-linker": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-4.0.0.tgz", + "integrity": "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==", + "optional": true, + "dependencies": { + "pug-error": "^2.0.0", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-load": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-3.0.0.tgz", + "integrity": "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==", + "optional": true, + "dependencies": { + "object-assign": "^4.1.1", + "pug-walk": "^2.0.0" + } + }, + "node_modules/pug-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-6.0.0.tgz", + "integrity": "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==", + "optional": true, + "dependencies": { + "pug-error": "^2.0.0", + "token-stream": "1.0.0" + } + }, + "node_modules/pug-runtime": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-3.0.1.tgz", + "integrity": "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==", + "optional": true + }, + "node_modules/pug-strip-comments": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-2.0.0.tgz", + "integrity": "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==", + "optional": true, + "dependencies": { + "pug-error": "^2.0.0" + } + }, + "node_modules/pug-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-2.0.0.tgz", + "integrity": "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==", + "optional": true + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -10516,6 +12513,15 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -10619,6 +12625,36 @@ "node": ">= 0.8" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "optional": true + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -10648,7 +12684,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, + "devOptional": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -10660,7 +12696,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8.6" }, @@ -10694,6 +12730,12 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "optional": true + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -10712,6 +12754,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "optional": true, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", @@ -10742,7 +12793,7 @@ "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, + "devOptional": true, "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", @@ -10888,6 +12939,139 @@ "node": ">=8" } }, + "node_modules/run-applescript": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-3.2.0.tgz", + "integrity": "sha512-Ep0RsvAjnRcBX1p5vogbaBdAGu/8j/ewpvGqnQYunnLd9SM0vWcPJewPKNnWFggf0hF0pwIgwV5XK7qQ7UZ8Qg==", + "optional": true, + "dependencies": { + "execa": "^0.10.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/run-applescript/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "optional": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/run-applescript/node_modules/execa": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", + "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", + "optional": true, + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/run-applescript/node_modules/get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/run-applescript/node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/run-applescript/node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "optional": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/run-applescript/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/run-applescript/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/run-applescript/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "optional": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/run-applescript/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/run-applescript/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==", + "optional": true + }, + "node_modules/run-applescript/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/run-async": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", @@ -11050,6 +13234,18 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "node_modules/selderee": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz", + "integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==", + "optional": true, + "dependencies": { + "parseley": "^0.12.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/semver": { "version": "7.6.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", @@ -11340,6 +13536,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/slick": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/slick/-/slick-1.12.2.tgz", + "integrity": "sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==", + "optional": true, + "engines": { + "node": "*" + } + }, "node_modules/smob": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", @@ -11581,6 +13786,15 @@ "node": ">=8" } }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", @@ -11617,7 +13831,7 @@ "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, + "devOptional": true, "engines": { "node": ">= 0.4" }, @@ -11900,6 +14114,15 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, + "node_modules/tlds": { + "version": "1.252.0", + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.252.0.tgz", + "integrity": "sha512-GA16+8HXvqtfEnw/DTcwB0UU354QE1n3+wh08oFjr6Znl7ZLAeUgYzCcK+/CCrOyE0vnHR8/pu3XXG3vDijXpQ==", + "optional": true, + "bin": { + "tlds": "bin.js" + } + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -11922,7 +14145,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, + "devOptional": true, "engines": { "node": ">=4" } @@ -11946,6 +14169,12 @@ "node": ">=0.6" } }, + "node_modules/token-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-1.0.0.tgz", + "integrity": "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==", + "optional": true + }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -12573,6 +14802,24 @@ "node": ">=14.17" } }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "optional": true + }, + "node_modules/uglify-js": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.0.tgz", + "integrity": "sha512-wNKHUY2hYYkf6oSFfhwwiHo4WCHzHmzcXsqXYTN9ja3iApYIFbb2U6ics9hBcYLHcYGQoAlwnZlTrf3oF+BL/Q==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/uid": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", @@ -12663,6 +14910,12 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==", + "optional": true + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -12717,6 +14970,15 @@ "node": ">=10.12.0" } }, + "node_modules/valid-data-url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-3.0.1.tgz", + "integrity": "sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==", + "optional": true, + "engines": { + "node": ">=10" + } + }, "node_modules/validator": { "version": "13.12.0", "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", @@ -12733,6 +14995,15 @@ "node": ">= 0.8" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", @@ -12764,6 +15035,132 @@ "defaults": "^1.0.3" } }, + "node_modules/web-resource-inliner": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-6.0.1.tgz", + "integrity": "sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==", + "optional": true, + "dependencies": { + "ansi-colors": "^4.1.1", + "escape-goat": "^3.0.0", + "htmlparser2": "^5.0.0", + "mime": "^2.4.6", + "node-fetch": "^2.6.0", + "valid-data-url": "^3.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/web-resource-inliner/node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "optional": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/web-resource-inliner/node_modules/dom-serializer/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "optional": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/web-resource-inliner/node_modules/domhandler": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz", + "integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==", + "optional": true, + "dependencies": { + "domelementtype": "^2.0.1" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/web-resource-inliner/node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "optional": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/web-resource-inliner/node_modules/domutils/node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "optional": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/web-resource-inliner/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "optional": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/web-resource-inliner/node_modules/htmlparser2": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-5.0.1.tgz", + "integrity": "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==", + "optional": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^3.3.0", + "domutils": "^2.4.2", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/fb55/htmlparser2?sponsor=1" + } + }, + "node_modules/web-resource-inliner/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -12925,6 +15322,21 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "node_modules/with": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/with/-/with-7.0.2.tgz", + "integrity": "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==", + "optional": true, + "dependencies": { + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "assert-never": "^1.2.1", + "babel-walk": "3.0.0-canary-5" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -12934,6 +15346,12 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "optional": true + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/package.json b/package.json index 6312e7629..38c90d3fc 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ }, "dependencies": { "@faker-js/faker": "^8.4.1", + "@nestjs-modules/mailer": "^2.0.2", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.2.3", "@nestjs/core": "^10.0.0", @@ -46,6 +47,7 @@ "class-validator": "^0.14.1", "joi": "^17.6.0", "nestjs-pino": "^4.1.0", + "nodemailer": "^6.9.14", "passport-jwt": "^4.0.1", "pg": "^8.12.0", "reflect-metadata": "^0.2.2", @@ -104,4 +106,4 @@ "lint-staged": { "**/*": "prettier --write --ignore-unknown" } -} \ No newline at end of file +} diff --git a/src/app.module.ts b/src/app.module.ts index 8a65ac9c8..4ecc28bcc 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -10,7 +10,10 @@ import { SeedingModule } from './database/seeding/seeding.module'; import HealthController from './health.controller'; import { AuthModule } from './modules/auth/auth.module'; import { UserModule } from './modules/user/user.module'; +import { EmailModule } from './modules/email/email.module'; import authConfig from 'config/auth.config'; +import { MailerModule } from '@nestjs-modules/mailer'; +import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter'; @Module({ providers: [ @@ -57,6 +60,31 @@ import authConfig from 'config/auth.config'; SeedingModule, AuthModule, UserModule, + EmailModule, + MailerModule.forRootAsync({ + imports: [ConfigModule], + useFactory: async (configService: ConfigService) => ({ + transport: { + host: configService.get('SMTP_HOST'), + port: configService.get('SMTP_PORT'), + auth: { + user: configService.get('SMTP_USER'), + pass: configService.get('SMTP_PASSWORD'), + }, + }, + defaults: { + from: `"Team Remote Bingo" <${configService.get('SMTP_USER')}>`, + }, + template: { + dir: process.cwd() + '/src/modules/email/templates', + adapter: new HandlebarsAdapter(), + options: { + strict: true, + }, + }, + }), + inject: [ConfigService], + }), ], controllers: [HealthController], }) diff --git a/src/modules/email/article.interface.ts b/src/modules/email/article.interface.ts new file mode 100644 index 000000000..072722331 --- /dev/null +++ b/src/modules/email/article.interface.ts @@ -0,0 +1,5 @@ +export interface Article { + title: string; + description: string; + link: string; +} diff --git a/src/modules/email/email.controller.spec.ts b/src/modules/email/email.controller.spec.ts new file mode 100644 index 000000000..a6b02b4cb --- /dev/null +++ b/src/modules/email/email.controller.spec.ts @@ -0,0 +1,32 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { EmailController } from './email.controller'; +import { EmailService } from './email.service'; +import { MailerService } from '@nestjs-modules/mailer'; + +describe('EmailController', () => { + let controller: EmailController; + let mailerService: MailerService; + + const mockMailerService = { + sendMail: jest.fn().mockResolvedValue({}), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [EmailController], + providers: [ + EmailService, + { + provide: MailerService, + useValue: mockMailerService, + }, + ], + }).compile(); + + controller = module.get(EmailController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/modules/email/email.controller.ts b/src/modules/email/email.controller.ts new file mode 100644 index 000000000..1a0100be6 --- /dev/null +++ b/src/modules/email/email.controller.ts @@ -0,0 +1,7 @@ +import { Controller } from '@nestjs/common'; +import { EmailService } from './email.service'; + +@Controller('email') +export class EmailController { + constructor(private readonly emailService: EmailService) {} +} diff --git a/src/modules/email/email.module.ts b/src/modules/email/email.module.ts new file mode 100644 index 000000000..38231e06a --- /dev/null +++ b/src/modules/email/email.module.ts @@ -0,0 +1,11 @@ +// src/email/email.module.ts +import { Module } from '@nestjs/common'; +import { EmailController } from './email.controller'; +import { EmailService } from './email.service'; + +@Module({ + providers: [EmailService], + exports: [EmailService], + controllers: [EmailController], +}) +export class EmailModule {} diff --git a/src/modules/email/email.service.spec.ts b/src/modules/email/email.service.spec.ts new file mode 100644 index 000000000..9f0fa4cab --- /dev/null +++ b/src/modules/email/email.service.spec.ts @@ -0,0 +1,113 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { EmailService } from './email.service'; +import { MailerService } from '@nestjs-modules/mailer'; + +describe('EmailService', () => { + let service: EmailService; + let mailerService: MailerService; + + const mockMailerService = { + sendMail: jest.fn().mockResolvedValue({}), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + EmailService, + { + provide: MailerService, + useValue: mockMailerService, + }, + ], + }).compile(); + + service = module.get(EmailService); + mailerService = module.get(MailerService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should send user confirmation email', async () => { + const email = 'test@example.com'; + const url = 'http://example.com/confirm'; + const token = 'mike'; + const link = `${url}?token=${token}`; + + await service.sendUserConfirmation(email, url, token); + + expect(mailerService.sendMail).toHaveBeenCalledWith({ + to: email, + subject: 'Welcome to My App! Confirm your Email', + template: 'confirmation', + context: { + link, + email, + }, + }); + }); + + it('should send forgot password email', async () => { + const email = 'test@example.com'; + const url = 'http://example.com/reset'; + const token = 'mike'; + const link = `${url}?token=${token}`; + + await service.sendForgotPassword(email, url, token); + + expect(mailerService.sendMail).toHaveBeenCalledWith({ + to: email, + subject: 'Reset Password', + template: 'reset-password', + context: { + link, + email, + }, + }); + }); + + it('should send waitlist confirmation email', async () => { + const email = 'test@example.com'; + const url = 'http://example.com/waitlist'; + + await service.sendWaitListMail(email, url); + + expect(mailerService.sendMail).toHaveBeenCalledWith({ + to: email, + subject: 'Waitlist Confirmation', + template: 'waitlist', + context: { + url, + email, + }, + }); + }); + + it('should send newsletter email', async () => { + const email = 'test@example.com'; + const articles = [ + { + title: 'Article Title 1', + description: 'Short description of the article.', + link: 'https://example.com/article1', + }, + { + title: 'Article Title 2', + description: 'Short description of the article.', + link: 'https://example.com/article2', + }, + ]; + await service.sendNewsLetterMail(email, articles); + + expect(mailerService.sendMail).toHaveBeenCalledWith({ + to: email, + subject: 'Monthly Newsletter', + template: 'newsletter', + context: { + email, + articles, + }, + }); + }); +}); diff --git a/src/modules/email/email.service.ts b/src/modules/email/email.service.ts new file mode 100644 index 000000000..f1d28107d --- /dev/null +++ b/src/modules/email/email.service.ts @@ -0,0 +1,59 @@ +// src/email/email.service.ts +import { Injectable } from '@nestjs/common'; +import { MailerService } from '@nestjs-modules/mailer'; +import { Article } from './article.interface'; + +@Injectable() +export class EmailService { + constructor(private readonly mailerService: MailerService) {} + + async sendUserConfirmation(email: string, url: string, token: string) { + const link = `${url}?token=${token}`; + await this.mailerService.sendMail({ + to: email, + subject: 'Welcome to My App! Confirm your Email', + template: 'confirmation', + context: { + link, + email, + }, + }); + } + + async sendForgotPassword(email: string, url: string, token: string) { + const link = `${url}?token=${token}`; + await this.mailerService.sendMail({ + to: email, + subject: 'Reset Password', + template: 'reset-password', + context: { + link, + email, + }, + }); + } + + async sendWaitListMail(email: string, url: string) { + await this.mailerService.sendMail({ + to: email, + subject: 'Waitlist Confirmation', + template: 'waitlist', + context: { + url, + email, + }, + }); + } + + async sendNewsLetterMail(email: string, articles: Article[]) { + await this.mailerService.sendMail({ + to: email, + subject: 'Monthly Newsletter', + template: 'newsletter', + context: { + email, + articles, + }, + }); + } +} diff --git a/src/modules/email/templates/confirmation.hbs b/src/modules/email/templates/confirmation.hbs new file mode 100644 index 000000000..3b0e92082 --- /dev/null +++ b/src/modules/email/templates/confirmation.hbs @@ -0,0 +1,40 @@ + + + + + Email Confirmation + + + +
+
+ Company Logo +
+
+

Confirm Your Email Address

+

Hello {{email}},

+

Thank you for registering. Please confirm your email address by clicking the button below:

+ Confirm Email +

If you didn't create an account with us, you can safely ignore this email.

+
+ +
+ + \ No newline at end of file diff --git a/src/modules/email/templates/newsletter.hbs b/src/modules/email/templates/newsletter.hbs new file mode 100644 index 000000000..7e1c5f5b4 --- /dev/null +++ b/src/modules/email/templates/newsletter.hbs @@ -0,0 +1,43 @@ + + + + + Newsletter + + + +
+
+ Company Logo +
+
+

Monthly Newsletter

+

Hello {{email}},

+ {{#each articles}} +
+

{{this.title}}

+

{{this.description}} Read more

+
+ {{/each}} +
+ +
+ + \ No newline at end of file diff --git a/src/modules/email/templates/reset-password.hbs b/src/modules/email/templates/reset-password.hbs new file mode 100644 index 000000000..e22e527a3 --- /dev/null +++ b/src/modules/email/templates/reset-password.hbs @@ -0,0 +1,40 @@ + + + + + Forgot Password + + + +
+
+ Company Logo +
+
+

Forgot Password

+

Hello {{email}},

+

It seems like you forgot your password. Click the button below to reset it:

+ Reset Password +

If you didn't request a password reset, please ignore this email.

+
+ +
+ + \ No newline at end of file diff --git a/src/modules/email/templates/waitlist.hbs b/src/modules/email/templates/waitlist.hbs new file mode 100644 index 000000000..22d0e98bd --- /dev/null +++ b/src/modules/email/templates/waitlist.hbs @@ -0,0 +1,39 @@ + + + + + Waitlist Confirmation + + + +
+
+ Company Logo +
+
+

You're on the Waitlist!

+

Hello {{email}},

+

Thank you for your interest! You've been added to our waitlist. We'll notify you as soon as we launch.

+ Visit Our Website +
+ +
+ + \ No newline at end of file From e87ae53d3dba49452bc1d2b1d79e30d74117674e Mon Sep 17 00:00:00 2001 From: Ayomide Date: Wed, 24 Jul 2024 11:38:57 +0100 Subject: [PATCH 07/18] feat: update organization endpoint --- src/app.module.ts | 1 - src/database/seeding/seeding.service.ts | 2 +- .../dto/create-organisation.dto.ts | 1 - .../entities/org-preferences.entity.ts | 15 ---- .../entities/organisation.entity.ts | 51 ------------- .../organisation/organisation.controller.ts | 48 ------------ .../organisation/organisation.module.ts | 12 --- .../organisation/organisation.service.ts | 73 ------------------ .../tests/organisation.service.spec.ts | 75 ------------------ .../dto/update-organisation.dto.ts | 3 +- .../organisations/organisations.controller.ts | 24 +++++- .../organisations/organisations.service.ts | 37 ++++++++- .../tests/organisations.service.spec.ts | 76 ++++++++++++++++++- src/modules/user/entities/user.entity.ts | 7 -- 14 files changed, 136 insertions(+), 289 deletions(-) delete mode 100644 src/modules/organisation/dto/create-organisation.dto.ts delete mode 100644 src/modules/organisation/entities/org-preferences.entity.ts delete mode 100644 src/modules/organisation/entities/organisation.entity.ts delete mode 100644 src/modules/organisation/organisation.controller.ts delete mode 100644 src/modules/organisation/organisation.module.ts delete mode 100644 src/modules/organisation/organisation.service.ts delete mode 100644 src/modules/organisation/tests/organisation.service.spec.ts rename src/modules/{organisation => organisations}/dto/update-organisation.dto.ts (87%) diff --git a/src/app.module.ts b/src/app.module.ts index 479d49779..1b42dbec2 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -7,7 +7,6 @@ import { LoggerModule } from 'nestjs-pino'; import { TypeOrmModule } from '@nestjs/typeorm'; import dataSource from './database/data-source'; import { SeedingModule } from './database/seeding/seeding.module'; -import { OrganisationModule } from './modules/organisation/organisation.module'; import HealthController from './health.controller'; import { AuthModule } from './modules/auth/auth.module'; import { UserModule } from './modules/user/user.module'; diff --git a/src/database/seeding/seeding.service.ts b/src/database/seeding/seeding.service.ts index cd5c61f16..7481d8398 100644 --- a/src/database/seeding/seeding.service.ts +++ b/src/database/seeding/seeding.service.ts @@ -1,7 +1,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { DataSource } from 'typeorm'; import { User } from '../../modules/user/entities/user.entity'; -import { Organisation } from '../../modules/organisation/entities/organisation.entity'; +import { Organisation } from '../../modules/organisations/entities/organisations.entity'; @Injectable() export class SeedingService { diff --git a/src/modules/organisation/dto/create-organisation.dto.ts b/src/modules/organisation/dto/create-organisation.dto.ts deleted file mode 100644 index 92183745a..000000000 --- a/src/modules/organisation/dto/create-organisation.dto.ts +++ /dev/null @@ -1 +0,0 @@ -export class CreateOrganisationDto {} diff --git a/src/modules/organisation/entities/org-preferences.entity.ts b/src/modules/organisation/entities/org-preferences.entity.ts deleted file mode 100644 index cd857a26b..000000000 --- a/src/modules/organisation/entities/org-preferences.entity.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne } from 'typeorm'; -import { AbstractBaseEntity } from './../../../entities/base.entity'; -import { Organisation } from './organisation.entity'; - -@Entity() -export class OrganisationPreference extends AbstractBaseEntity { - @Column({ nullable: false }) - name: string; - - @Column({ nullable: false }) - value: string; - - @ManyToOne(() => Organisation, organisation => organisation.preferences, { nullable: false, onDelete: 'CASCADE' }) - organisation: Organisation; -} diff --git a/src/modules/organisation/entities/organisation.entity.ts b/src/modules/organisation/entities/organisation.entity.ts deleted file mode 100644 index ab3b018ed..000000000 --- a/src/modules/organisation/entities/organisation.entity.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { - Entity, - PrimaryGeneratedColumn, - Column, - CreateDateColumn, - UpdateDateColumn, - ManyToOne, - OneToMany, -} from 'typeorm'; -import { User } from '../../user/entities/user.entity'; -import { OrganisationPreference } from './org-preferences.entity'; -import { AbstractBaseEntity } from '../../../entities/base.entity'; - -@Entity() -export class Organisation extends AbstractBaseEntity { - @Column({ nullable: false }) - name: string; - - @Column('text', { nullable: false }) - description: string; - - @Column({ unique: true, nullable: false }) - email: string; - - @Column({ nullable: false }) - industry: string; - - @Column({ nullable: false }) - type: string; - - @Column({ nullable: false }) - country: string; - - @Column('text', { nullable: false }) - address: string; - - @ManyToOne(() => User, user => user.owned_organisations, { nullable: false }) - owner: User; - - @Column({ nullable: false }) - state: string; - - @ManyToOne(() => User, user => user.created_organisations, { nullable: false }) - creator: User; - - @Column('boolean', { default: false, nullable: false }) - isDeleted: boolean; - - @OneToMany(() => OrganisationPreference, preference => preference.organisation) - preferences: OrganisationPreference[]; -} diff --git a/src/modules/organisation/organisation.controller.ts b/src/modules/organisation/organisation.controller.ts deleted file mode 100644 index d95fc40e7..000000000 --- a/src/modules/organisation/organisation.controller.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Controller, Get, Post, Body, Patch, Param, Delete, HttpException } from '@nestjs/common'; -import { OrganisationService } from './organisation.service'; -import { CreateOrganisationDto } from './dto/create-organisation.dto'; -import { UpdateOrganisationDto } from './dto/update-organisation.dto'; -import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; - -@ApiBearerAuth() -@ApiTags('Organisation') -@Controller('organisation') -export class OrganisationController { - constructor(private readonly organisationService: OrganisationService) {} - - @Post() - create(@Body() createOrganisationDto: CreateOrganisationDto) { - return this.organisationService.create(createOrganisationDto); - } - - @Get() - findAll() { - return this.organisationService.findAll(); - } - - @Get(':id') - findOne(@Param('id') id: string) { - return this.organisationService.findOne(+id); - } - - @ApiOperation({ summary: 'Update Organisation' }) - @ApiResponse({ - status: 200, - description: 'The found record', - type: UpdateOrganisationDto, - }) - @Patch(':id') - async update(@Param('id') id: string, @Body() updateOrganisationDto: UpdateOrganisationDto) { - try { - const updatedOrg = await this.organisationService.update(id, updateOrganisationDto); - return { message: 'Product successfully updated', org: updatedOrg }; - } catch (error) { - throw new HttpException(error.message, error.status || 500); - } - } - - @Delete(':id') - remove(@Param('id') id: string) { - return this.organisationService.remove(+id); - } -} diff --git a/src/modules/organisation/organisation.module.ts b/src/modules/organisation/organisation.module.ts deleted file mode 100644 index df276e6a3..000000000 --- a/src/modules/organisation/organisation.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Module } from '@nestjs/common'; -import { OrganisationService } from './organisation.service'; -import { OrganisationController } from './organisation.controller'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Organisation } from './entities/organisation.entity'; - -@Module({ - imports: [TypeOrmModule.forFeature([Organisation])], - controllers: [OrganisationController], - providers: [OrganisationService], -}) -export class OrganisationModule {} diff --git a/src/modules/organisation/organisation.service.ts b/src/modules/organisation/organisation.service.ts deleted file mode 100644 index 8fa95897b..000000000 --- a/src/modules/organisation/organisation.service.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { - BadRequestException, - HttpException, - HttpStatus, - Injectable, - InternalServerErrorException, - NotFoundException, -} from '@nestjs/common'; -import { CreateOrganisationDto } from './dto/create-organisation.dto'; -import { UpdateOrganisationDto } from './dto/update-organisation.dto'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { Organisation } from './entities/organisation.entity'; - -@Injectable() -export class OrganisationService { - constructor( - @InjectRepository(Organisation) - private organisationRepository: Repository - ) {} - - create(createOrganisationDto: CreateOrganisationDto) { - return 'This action adds a new organisation'; - } - - async findAll() { - try { - const orgs = await this.organisationRepository.find(); - console.log(orgs); - - return orgs; - } catch (error) { - console.log(error.message); - } - throw new Error(`Internal error`); - } - - findOne(id: number) { - return `This action returns a #${id} organisation`; - } - - async update( - id: string, - updateOrganisationDto: UpdateOrganisationDto - ): Promise<{ message: string; org: Organisation }> { - try { - const org = await this.organisationRepository.findOneBy({ id }); - - if (!org) { - throw new NotFoundException('Organization not found'); - } - - await this.organisationRepository.update(id, updateOrganisationDto); - - const updatedOrg = await this.organisationRepository.findOneBy({ id }); - - if (!updatedOrg) { - throw new BadRequestException('Error updating organization'); - } - - return { message: 'Product successfully updated', org: updatedOrg }; - } catch (error) { - if (error instanceof NotFoundException || error instanceof BadRequestException) { - throw error; - } - throw new InternalServerErrorException(`An internal server error occurred: ${error.message}`); - } - } - - remove(id: number) { - return `This action removes a #${id} organisation`; - } -} diff --git a/src/modules/organisation/tests/organisation.service.spec.ts b/src/modules/organisation/tests/organisation.service.spec.ts deleted file mode 100644 index dca006cec..000000000 --- a/src/modules/organisation/tests/organisation.service.spec.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { OrganisationService } from '../organisation.service'; -import { getRepositoryToken } from '@nestjs/typeorm'; -import { Organisation } from '../entities/organisation.entity'; -import { Repository } from 'typeorm'; -import { NotFoundException, BadRequestException, InternalServerErrorException } from '@nestjs/common'; - -describe('OrganisationService', () => { - let service: OrganisationService; - let repository: Repository; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - OrganisationService, - { - provide: getRepositoryToken(Organisation), - useClass: Repository, - }, - ], - }).compile(); - - service = module.get(OrganisationService); - repository = module.get>(getRepositoryToken(Organisation)); - }); - - describe('update', () => { - it('should update an organisation successfully', async () => { - const id = '1'; - const updateOrganisationDto = { name: 'New Name', description: 'Updated Description' }; - const organisation = new Organisation(); - - jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(organisation); - jest.spyOn(repository, 'update').mockResolvedValueOnce({ affected: 1 } as any); - jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce({ ...organisation, ...updateOrganisationDto }); - - const result = await service.update(id, updateOrganisationDto); - - expect(result).toEqual({ - message: 'Product successfully updated', - org: { ...organisation, ...updateOrganisationDto }, - }); - }); - - it('should throw NotFoundException if organisation not found', async () => { - const id = '1'; - const updateOrganisationDto = { name: 'New Name', description: 'Updated Description' }; - - jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(null); - - await expect(service.update(id, updateOrganisationDto)).rejects.toThrow(NotFoundException); - }); - - it('should throw BadRequestException if update fails', async () => { - const id = '1'; - const updateOrganisationDto = { name: 'New Name', description: 'Updated Description' }; - const organisation = new Organisation(); - - jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(organisation); - jest.spyOn(repository, 'update').mockResolvedValueOnce({ affected: 0 } as any); - jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(null); - - await expect(service.update(id, updateOrganisationDto)).rejects.toThrow(BadRequestException); - }); - - it('should throw InternalServerErrorException if an unexpected error occurs', async () => { - const id = '1'; - const updateOrganisationDto = { name: 'New Name', description: 'Updated Description' }; - - jest.spyOn(repository, 'findOneBy').mockRejectedValueOnce(new Error('Unexpected error')); - - await expect(service.update(id, updateOrganisationDto)).rejects.toThrow(InternalServerErrorException); - }); - }); -}); diff --git a/src/modules/organisation/dto/update-organisation.dto.ts b/src/modules/organisations/dto/update-organisation.dto.ts similarity index 87% rename from src/modules/organisation/dto/update-organisation.dto.ts rename to src/modules/organisations/dto/update-organisation.dto.ts index 49cc7d202..d9c438ee5 100644 --- a/src/modules/organisation/dto/update-organisation.dto.ts +++ b/src/modules/organisations/dto/update-organisation.dto.ts @@ -1,9 +1,8 @@ -import { CreateOrganisationDto } from './create-organisation.dto'; import { IsBoolean, IsEmail, IsOptional, IsString, MinLength } from 'class-validator'; import { User } from '../../user/entities/user.entity'; import { ApiProperty, PartialType } from '@nestjs/swagger'; -export class UpdateOrganisationDto extends PartialType(CreateOrganisationDto) { +export class UpdateOrganisationDto { @ApiProperty({ example: "CodeGhinux's Organisation", description: 'Name of organisation', diff --git a/src/modules/organisations/organisations.controller.ts b/src/modules/organisations/organisations.controller.ts index 190de51d7..1e399859c 100644 --- a/src/modules/organisations/organisations.controller.ts +++ b/src/modules/organisations/organisations.controller.ts @@ -1,8 +1,13 @@ -import { Body, Controller, Post, Request, UseGuards } from '@nestjs/common'; +import { Body, Controller, HttpException, Param, Patch, Post, Request, UseGuards } from '@nestjs/common'; import { OrganisationsService } from './organisations.service'; import { OrganisationRequestDto } from './dto/organisation.dto'; import { AuthGuard } from '@nestjs/passport'; +import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { UpdateOrganisationDto } from './dto/update-organisation.dto'; +import { skipAuth } from 'src/helpers/skipAuth'; +@ApiBearerAuth() +@ApiTags('Organisation') @Controller('organisations') export class OrganisationsController { constructor(private readonly organisationsService: OrganisationsService) {} @@ -12,4 +17,21 @@ export class OrganisationsController { const user = req['user']; return this.organisationsService.create(createOrganisationDto, user.sub); } + + @ApiOperation({ summary: 'Update Organisation' }) + @ApiResponse({ + status: 200, + description: 'The found record', + type: UpdateOrganisationDto, + }) + @skipAuth() + @Patch(':id') + async update(@Param('id') id: string, @Body() updateOrganisationDto: UpdateOrganisationDto) { + try { + const updatedOrg = await this.organisationsService.update(id, updateOrganisationDto); + return { message: 'Product successfully updated', org: updatedOrg }; + } catch (error) { + throw new HttpException(error.message, error.status || 500); + } + } } diff --git a/src/modules/organisations/organisations.service.ts b/src/modules/organisations/organisations.service.ts index dff686e93..4fa15efc7 100644 --- a/src/modules/organisations/organisations.service.ts +++ b/src/modules/organisations/organisations.service.ts @@ -1,4 +1,10 @@ -import { Injectable, UnprocessableEntityException } from '@nestjs/common'; +import { + BadRequestException, + Injectable, + InternalServerErrorException, + NotFoundException, + UnprocessableEntityException, +} from '@nestjs/common'; import { Repository } from 'typeorm'; import { Organisation } from './entities/organisations.entity'; import { OrganisationRequestDto } from './dto/organisation.dto'; @@ -6,6 +12,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { User } from '../user/entities/user.entity'; import { OrganisationMapper } from './mapper/organisation.mapper'; import { CreateOrganisationMapper } from './mapper/create-organisation.mapper'; +import { UpdateOrganisationDto } from './dto/update-organisation.dto'; @Injectable() export class OrganisationsService { @@ -43,4 +50,32 @@ export class OrganisationsService { const emailFound = await this.organisationRepository.findBy({ email }); return emailFound?.length ? true : false; } + + async update( + id: string, + updateOrganisationDto: UpdateOrganisationDto + ): Promise<{ message: string; org: Organisation }> { + try { + const org = await this.organisationRepository.findOneBy({ id }); + + if (!org) { + throw new NotFoundException('Organization not found'); + } + + await this.organisationRepository.update(id, updateOrganisationDto); + + const updatedOrg = await this.organisationRepository.findOneBy({ id }); + + if (!updatedOrg) { + throw new BadRequestException('Error updating organization'); + } + + return { message: 'Product successfully updated', org: updatedOrg }; + } catch (error) { + if (error instanceof NotFoundException || error instanceof BadRequestException) { + throw error; + } + throw new InternalServerErrorException(`An internal server error occurred: ${error.message}`); + } + } } diff --git a/src/modules/organisations/tests/organisations.service.spec.ts b/src/modules/organisations/tests/organisations.service.spec.ts index 52170dfb8..5171909cf 100644 --- a/src/modules/organisations/tests/organisations.service.spec.ts +++ b/src/modules/organisations/tests/organisations.service.spec.ts @@ -8,7 +8,12 @@ import { validate } from 'class-validator'; import { orgMock } from '../tests/mocks/organisation.mock'; import { createMockOrganisationRequestDto } from '../tests/mocks/organisation-dto.mock'; import UserService from '../../user/user.service'; -import { UnprocessableEntityException } from '@nestjs/common'; +import { + BadRequestException, + InternalServerErrorException, + NotFoundException, + UnprocessableEntityException, +} from '@nestjs/common'; describe('OrganisationsService', () => { let service: OrganisationsService; @@ -88,3 +93,72 @@ describe('OrganisationsService', () => { }); }); }); + +describe('OrganisationService', () => { + let service: OrganisationsService; + let repository: Repository; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + OrganisationsService, + { + provide: getRepositoryToken(Organisation), + useClass: Repository, + }, + ], + }).compile(); + + service = module.get(OrganisationsService); + repository = module.get>(getRepositoryToken(Organisation)); + }); + + describe('update', () => { + it('should update an organisation successfully', async () => { + const id = '1'; + const updateOrganisationDto = { name: 'New Name', description: 'Updated Description' }; + const organisation = new Organisation(); + + jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(organisation); + jest.spyOn(repository, 'update').mockResolvedValueOnce({ affected: 1 } as any); + jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce({ ...organisation, ...updateOrganisationDto }); + + const result = await service.update(id, updateOrganisationDto); + + expect(result).toEqual({ + message: 'Product successfully updated', + org: { ...organisation, ...updateOrganisationDto }, + }); + }); + + it('should throw NotFoundException if organisation not found', async () => { + const id = '1'; + const updateOrganisationDto = { name: 'New Name', description: 'Updated Description' }; + + jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(null); + + await expect(service.update(id, updateOrganisationDto)).rejects.toThrow(NotFoundException); + }); + + it('should throw BadRequestException if update fails', async () => { + const id = '1'; + const updateOrganisationDto = { name: 'New Name', description: 'Updated Description' }; + const organisation = new Organisation(); + + jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(organisation); + jest.spyOn(repository, 'update').mockResolvedValueOnce({ affected: 0 } as any); + jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(null); + + await expect(service.update(id, updateOrganisationDto)).rejects.toThrow(BadRequestException); + }); + + it('should throw InternalServerErrorException if an unexpected error occurs', async () => { + const id = '1'; + const updateOrganisationDto = { name: 'New Name', description: 'Updated Description' }; + + jest.spyOn(repository, 'findOneBy').mockRejectedValueOnce(new Error('Unexpected error')); + + await expect(service.update(id, updateOrganisationDto)).rejects.toThrow(InternalServerErrorException); + }); + }); +}); diff --git a/src/modules/user/entities/user.entity.ts b/src/modules/user/entities/user.entity.ts index ace38b06f..c4ca19db5 100644 --- a/src/modules/user/entities/user.entity.ts +++ b/src/modules/user/entities/user.entity.ts @@ -1,13 +1,6 @@ import { BeforeInsert, Column, Entity, OneToMany } from 'typeorm'; import * as bcrypt from 'bcrypt'; import { AbstractBaseEntity } from '../../../entities/base.entity'; -import { Organisation } from '../../organisation/entities/organisation.entity'; - -export enum UserType { - SUPER_ADMIN = 'super_admin', - ADMIN = 'admin', - USER = 'vendor', -} import { Organisation } from '../../../modules/organisations/entities/organisations.entity'; export enum UserType { From 3c042f1bf22eb7a0c4472e167c4397342a3ff526 Mon Sep 17 00:00:00 2001 From: Ayomide Date: Wed, 24 Jul 2024 11:50:11 +0100 Subject: [PATCH 08/18] fix: update organization test cases --- .../organisations/organisations.controller.ts | 3 +- .../organisations/organisations.service.ts | 2 +- .../tests/organisations.service.spec.ts | 54 ++++++------------- 3 files changed, 18 insertions(+), 41 deletions(-) diff --git a/src/modules/organisations/organisations.controller.ts b/src/modules/organisations/organisations.controller.ts index 1e399859c..e0dac579c 100644 --- a/src/modules/organisations/organisations.controller.ts +++ b/src/modules/organisations/organisations.controller.ts @@ -24,12 +24,11 @@ export class OrganisationsController { description: 'The found record', type: UpdateOrganisationDto, }) - @skipAuth() @Patch(':id') async update(@Param('id') id: string, @Body() updateOrganisationDto: UpdateOrganisationDto) { try { const updatedOrg = await this.organisationsService.update(id, updateOrganisationDto); - return { message: 'Product successfully updated', org: updatedOrg }; + return { message: 'Organisation successfully updated', org: updatedOrg }; } catch (error) { throw new HttpException(error.message, error.status || 500); } diff --git a/src/modules/organisations/organisations.service.ts b/src/modules/organisations/organisations.service.ts index 4fa15efc7..2849fa993 100644 --- a/src/modules/organisations/organisations.service.ts +++ b/src/modules/organisations/organisations.service.ts @@ -70,7 +70,7 @@ export class OrganisationsService { throw new BadRequestException('Error updating organization'); } - return { message: 'Product successfully updated', org: updatedOrg }; + return { message: 'Organisation successfully updated', org: updatedOrg }; } catch (error) { if (error instanceof NotFoundException || error instanceof BadRequestException) { throw error; diff --git a/src/modules/organisations/tests/organisations.service.spec.ts b/src/modules/organisations/tests/organisations.service.spec.ts index 5171909cf..ffef61afa 100644 --- a/src/modules/organisations/tests/organisations.service.spec.ts +++ b/src/modules/organisations/tests/organisations.service.spec.ts @@ -31,6 +31,8 @@ describe('OrganisationsService', () => { findOne: jest.fn(), create: jest.fn(), save: jest.fn(), + findOneBy: jest.fn(), + update: jest.fn(), }, }, UserService, @@ -44,6 +46,7 @@ describe('OrganisationsService', () => { }, ], }).compile(); + service = module.get(OrganisationsService); userRepository = module.get>(getRepositoryToken(User)); organisationRepository = module.get>(getRepositoryToken(Organisation)); @@ -53,7 +56,7 @@ describe('OrganisationsService', () => { expect(service).toBeDefined(); }); - describe('it should create an organisation', () => { + describe('create organisation', () => { beforeEach(async () => { const errors = await validate(createMockOrganisationRequestDto()); expect(errors).toHaveLength(0); @@ -73,13 +76,6 @@ describe('OrganisationsService', () => { expect(result.status).toEqual('success'); expect(result.message).toEqual('organisation created successfully'); }); - }); - - describe('error for an exsiting email', () => { - beforeEach(async () => { - const errors = await validate(createMockOrganisationRequestDto()); - expect(errors).toHaveLength(0); - }); it('should throw an error if the email already exists', async () => { jest.spyOn(organisationRepository, 'findBy').mockResolvedValue([orgMock]); @@ -92,41 +88,23 @@ describe('OrganisationsService', () => { ); }); }); -}); - -describe('OrganisationService', () => { - let service: OrganisationsService; - let repository: Repository; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - OrganisationsService, - { - provide: getRepositoryToken(Organisation), - useClass: Repository, - }, - ], - }).compile(); - - service = module.get(OrganisationsService); - repository = module.get>(getRepositoryToken(Organisation)); - }); - describe('update', () => { + describe('update organisation', () => { it('should update an organisation successfully', async () => { const id = '1'; const updateOrganisationDto = { name: 'New Name', description: 'Updated Description' }; const organisation = new Organisation(); - jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(organisation); - jest.spyOn(repository, 'update').mockResolvedValueOnce({ affected: 1 } as any); - jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce({ ...organisation, ...updateOrganisationDto }); + jest.spyOn(organisationRepository, 'findOneBy').mockResolvedValueOnce(organisation); + jest.spyOn(organisationRepository, 'update').mockResolvedValueOnce({ affected: 1 } as any); + jest + .spyOn(organisationRepository, 'findOneBy') + .mockResolvedValueOnce({ ...organisation, ...updateOrganisationDto }); const result = await service.update(id, updateOrganisationDto); expect(result).toEqual({ - message: 'Product successfully updated', + message: 'Organisation successfully updated', org: { ...organisation, ...updateOrganisationDto }, }); }); @@ -135,7 +113,7 @@ describe('OrganisationService', () => { const id = '1'; const updateOrganisationDto = { name: 'New Name', description: 'Updated Description' }; - jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(null); + jest.spyOn(organisationRepository, 'findOneBy').mockResolvedValueOnce(null); await expect(service.update(id, updateOrganisationDto)).rejects.toThrow(NotFoundException); }); @@ -145,9 +123,9 @@ describe('OrganisationService', () => { const updateOrganisationDto = { name: 'New Name', description: 'Updated Description' }; const organisation = new Organisation(); - jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(organisation); - jest.spyOn(repository, 'update').mockResolvedValueOnce({ affected: 0 } as any); - jest.spyOn(repository, 'findOneBy').mockResolvedValueOnce(null); + jest.spyOn(organisationRepository, 'findOneBy').mockResolvedValueOnce(organisation); + jest.spyOn(organisationRepository, 'update').mockResolvedValueOnce({ affected: 0 } as any); + jest.spyOn(organisationRepository, 'findOneBy').mockResolvedValueOnce(null); await expect(service.update(id, updateOrganisationDto)).rejects.toThrow(BadRequestException); }); @@ -156,7 +134,7 @@ describe('OrganisationService', () => { const id = '1'; const updateOrganisationDto = { name: 'New Name', description: 'Updated Description' }; - jest.spyOn(repository, 'findOneBy').mockRejectedValueOnce(new Error('Unexpected error')); + jest.spyOn(organisationRepository, 'findOneBy').mockRejectedValueOnce(new Error('Unexpected error')); await expect(service.update(id, updateOrganisationDto)).rejects.toThrow(InternalServerErrorException); }); From 6912ebf0b1263ef19b45367544f19655a4242b9a Mon Sep 17 00:00:00 2001 From: Ayomide Date: Wed, 24 Jul 2024 12:15:28 +0100 Subject: [PATCH 09/18] fix: fix in update organization --- src/modules/organisations/organisations.service.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/modules/organisations/organisations.service.ts b/src/modules/organisations/organisations.service.ts index 2849fa993..290a27152 100644 --- a/src/modules/organisations/organisations.service.ts +++ b/src/modules/organisations/organisations.service.ts @@ -57,19 +57,12 @@ export class OrganisationsService { ): Promise<{ message: string; org: Organisation }> { try { const org = await this.organisationRepository.findOneBy({ id }); - if (!org) { throw new NotFoundException('Organization not found'); } - await this.organisationRepository.update(id, updateOrganisationDto); - const updatedOrg = await this.organisationRepository.findOneBy({ id }); - if (!updatedOrg) { - throw new BadRequestException('Error updating organization'); - } - return { message: 'Organisation successfully updated', org: updatedOrg }; } catch (error) { if (error instanceof NotFoundException || error instanceof BadRequestException) { From 6b656d45673fb5af9192e58a074340618fe359bb Mon Sep 17 00:00:00 2001 From: King-Mikaelson Date: Wed, 24 Jul 2024 12:19:38 +0100 Subject: [PATCH 10/18] feat(email-service): renamed interface name --- src/modules/email/article.interface.ts | 2 +- src/modules/email/email.service.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/email/article.interface.ts b/src/modules/email/article.interface.ts index 072722331..d3c6bd539 100644 --- a/src/modules/email/article.interface.ts +++ b/src/modules/email/article.interface.ts @@ -1,4 +1,4 @@ -export interface Article { +export interface ArticleInterface { title: string; description: string; link: string; diff --git a/src/modules/email/email.service.ts b/src/modules/email/email.service.ts index f1d28107d..b2d1dc90e 100644 --- a/src/modules/email/email.service.ts +++ b/src/modules/email/email.service.ts @@ -1,7 +1,7 @@ // src/email/email.service.ts import { Injectable } from '@nestjs/common'; import { MailerService } from '@nestjs-modules/mailer'; -import { Article } from './article.interface'; +import { ArticleInterface } from './article.interface'; @Injectable() export class EmailService { @@ -45,7 +45,7 @@ export class EmailService { }); } - async sendNewsLetterMail(email: string, articles: Article[]) { + async sendNewsLetterMail(email: string, articles: ArticleInterface[]) { await this.mailerService.sendMail({ to: email, subject: 'Monthly Newsletter', From 713ddca7dd0c4f7b00d8ab446387e907226cc775 Mon Sep 17 00:00:00 2001 From: King-Mikaelson Date: Wed, 24 Jul 2024 12:21:41 +0100 Subject: [PATCH 11/18] feat(email-service): renamed interface name --- src/modules/email/email.controller.spec.ts | 32 ---------------------- src/modules/email/email.controller.ts | 7 ----- src/modules/email/email.module.ts | 2 -- 3 files changed, 41 deletions(-) delete mode 100644 src/modules/email/email.controller.spec.ts delete mode 100644 src/modules/email/email.controller.ts diff --git a/src/modules/email/email.controller.spec.ts b/src/modules/email/email.controller.spec.ts deleted file mode 100644 index a6b02b4cb..000000000 --- a/src/modules/email/email.controller.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { EmailController } from './email.controller'; -import { EmailService } from './email.service'; -import { MailerService } from '@nestjs-modules/mailer'; - -describe('EmailController', () => { - let controller: EmailController; - let mailerService: MailerService; - - const mockMailerService = { - sendMail: jest.fn().mockResolvedValue({}), - }; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [EmailController], - providers: [ - EmailService, - { - provide: MailerService, - useValue: mockMailerService, - }, - ], - }).compile(); - - controller = module.get(EmailController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/modules/email/email.controller.ts b/src/modules/email/email.controller.ts deleted file mode 100644 index 1a0100be6..000000000 --- a/src/modules/email/email.controller.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Controller } from '@nestjs/common'; -import { EmailService } from './email.service'; - -@Controller('email') -export class EmailController { - constructor(private readonly emailService: EmailService) {} -} diff --git a/src/modules/email/email.module.ts b/src/modules/email/email.module.ts index 38231e06a..d016c7dd8 100644 --- a/src/modules/email/email.module.ts +++ b/src/modules/email/email.module.ts @@ -1,11 +1,9 @@ // src/email/email.module.ts import { Module } from '@nestjs/common'; -import { EmailController } from './email.controller'; import { EmailService } from './email.service'; @Module({ providers: [EmailService], exports: [EmailService], - controllers: [EmailController], }) export class EmailModule {} From 4ee07d3aa3afeaee1cb45004d7ba2e39620d1fa3 Mon Sep 17 00:00:00 2001 From: King-Mikaelson Date: Wed, 24 Jul 2024 12:23:35 +0100 Subject: [PATCH 12/18] feat(email-service): renamed interface name --- src/modules/email/email.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/email/email.service.ts b/src/modules/email/email.service.ts index b2d1dc90e..5d81dd3af 100644 --- a/src/modules/email/email.service.ts +++ b/src/modules/email/email.service.ts @@ -7,7 +7,7 @@ import { ArticleInterface } from './article.interface'; export class EmailService { constructor(private readonly mailerService: MailerService) {} - async sendUserConfirmation(email: string, url: string, token: string) { + async sendUserConfirmationMail(email: string, url: string, token: string) { const link = `${url}?token=${token}`; await this.mailerService.sendMail({ to: email, @@ -20,7 +20,7 @@ export class EmailService { }); } - async sendForgotPassword(email: string, url: string, token: string) { + async sendForgotPasswordMail(email: string, url: string, token: string) { const link = `${url}?token=${token}`; await this.mailerService.sendMail({ to: email, From 2a593696a74c5ae003fbd5965bdee3be3e6b827d Mon Sep 17 00:00:00 2001 From: Ayomide Date: Wed, 24 Jul 2024 12:29:31 +0100 Subject: [PATCH 13/18] fix: fix in update organization --- .../tests/organisations.service.spec.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/modules/organisations/tests/organisations.service.spec.ts b/src/modules/organisations/tests/organisations.service.spec.ts index ffef61afa..cd3df9547 100644 --- a/src/modules/organisations/tests/organisations.service.spec.ts +++ b/src/modules/organisations/tests/organisations.service.spec.ts @@ -118,18 +118,6 @@ describe('OrganisationsService', () => { await expect(service.update(id, updateOrganisationDto)).rejects.toThrow(NotFoundException); }); - it('should throw BadRequestException if update fails', async () => { - const id = '1'; - const updateOrganisationDto = { name: 'New Name', description: 'Updated Description' }; - const organisation = new Organisation(); - - jest.spyOn(organisationRepository, 'findOneBy').mockResolvedValueOnce(organisation); - jest.spyOn(organisationRepository, 'update').mockResolvedValueOnce({ affected: 0 } as any); - jest.spyOn(organisationRepository, 'findOneBy').mockResolvedValueOnce(null); - - await expect(service.update(id, updateOrganisationDto)).rejects.toThrow(BadRequestException); - }); - it('should throw InternalServerErrorException if an unexpected error occurs', async () => { const id = '1'; const updateOrganisationDto = { name: 'New Name', description: 'Updated Description' }; From e6fea585a0483079947e36c0b3d90e5c65ffc0e4 Mon Sep 17 00:00:00 2001 From: Ayomide Date: Wed, 24 Jul 2024 12:45:40 +0100 Subject: [PATCH 14/18] fix: fix in update organization --- src/modules/organisations/organisations.controller.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/modules/organisations/organisations.controller.ts b/src/modules/organisations/organisations.controller.ts index e0dac579c..3bc6adf7a 100644 --- a/src/modules/organisations/organisations.controller.ts +++ b/src/modules/organisations/organisations.controller.ts @@ -26,11 +26,7 @@ export class OrganisationsController { }) @Patch(':id') async update(@Param('id') id: string, @Body() updateOrganisationDto: UpdateOrganisationDto) { - try { - const updatedOrg = await this.organisationsService.update(id, updateOrganisationDto); - return { message: 'Organisation successfully updated', org: updatedOrg }; - } catch (error) { - throw new HttpException(error.message, error.status || 500); - } + const updatedOrg = await this.organisationsService.update(id, updateOrganisationDto); + return { message: 'Organisation successfully updated', org: updatedOrg }; } } From d8a94e37ece4beebaf2e30d162af45ddd2b4df94 Mon Sep 17 00:00:00 2001 From: King-Mikaelson Date: Wed, 24 Jul 2024 12:54:47 +0100 Subject: [PATCH 15/18] feat(email-service): fixed tests issue --- src/modules/email/email.service.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/email/email.service.spec.ts b/src/modules/email/email.service.spec.ts index 9f0fa4cab..d4d6ff5cb 100644 --- a/src/modules/email/email.service.spec.ts +++ b/src/modules/email/email.service.spec.ts @@ -35,7 +35,7 @@ describe('EmailService', () => { const token = 'mike'; const link = `${url}?token=${token}`; - await service.sendUserConfirmation(email, url, token); + await service.sendUserConfirmationMail(email, url, token); expect(mailerService.sendMail).toHaveBeenCalledWith({ to: email, @@ -54,7 +54,7 @@ describe('EmailService', () => { const token = 'mike'; const link = `${url}?token=${token}`; - await service.sendForgotPassword(email, url, token); + await service.sendForgotPasswordMail(email, url, token); expect(mailerService.sendMail).toHaveBeenCalledWith({ to: email, From 775896fd8f83d5e5af03788725dc3e9423bbc426 Mon Sep 17 00:00:00 2001 From: King-Mikaelson Date: Wed, 24 Jul 2024 12:57:35 +0100 Subject: [PATCH 16/18] feat(email-service): removed comments --- src/modules/email/email.module.ts | 1 - src/modules/email/email.service.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/modules/email/email.module.ts b/src/modules/email/email.module.ts index d016c7dd8..fff2146de 100644 --- a/src/modules/email/email.module.ts +++ b/src/modules/email/email.module.ts @@ -1,4 +1,3 @@ -// src/email/email.module.ts import { Module } from '@nestjs/common'; import { EmailService } from './email.service'; diff --git a/src/modules/email/email.service.ts b/src/modules/email/email.service.ts index 5d81dd3af..4e19f292c 100644 --- a/src/modules/email/email.service.ts +++ b/src/modules/email/email.service.ts @@ -1,4 +1,3 @@ -// src/email/email.service.ts import { Injectable } from '@nestjs/common'; import { MailerService } from '@nestjs-modules/mailer'; import { ArticleInterface } from './article.interface'; From 000a2ce30a1c14a122afb347e1db3c41430cdc83 Mon Sep 17 00:00:00 2001 From: Ayomide Date: Wed, 24 Jul 2024 13:11:49 +0100 Subject: [PATCH 17/18] fix: fix in update organization --- src/modules/organisations/organisations.controller.ts | 2 +- src/modules/organisations/organisations.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/organisations/organisations.controller.ts b/src/modules/organisations/organisations.controller.ts index 3bc6adf7a..a52af1aca 100644 --- a/src/modules/organisations/organisations.controller.ts +++ b/src/modules/organisations/organisations.controller.ts @@ -26,7 +26,7 @@ export class OrganisationsController { }) @Patch(':id') async update(@Param('id') id: string, @Body() updateOrganisationDto: UpdateOrganisationDto) { - const updatedOrg = await this.organisationsService.update(id, updateOrganisationDto); + const updatedOrg = await this.organisationsService.updateOrganisation(id, updateOrganisationDto); return { message: 'Organisation successfully updated', org: updatedOrg }; } } diff --git a/src/modules/organisations/organisations.service.ts b/src/modules/organisations/organisations.service.ts index 290a27152..f2c8e30d7 100644 --- a/src/modules/organisations/organisations.service.ts +++ b/src/modules/organisations/organisations.service.ts @@ -51,7 +51,7 @@ export class OrganisationsService { return emailFound?.length ? true : false; } - async update( + async updateOrganisation( id: string, updateOrganisationDto: UpdateOrganisationDto ): Promise<{ message: string; org: Organisation }> { From 3d23b3a8ccefaaf61c274844c79812b525312747 Mon Sep 17 00:00:00 2001 From: Ayomide Date: Wed, 24 Jul 2024 13:23:39 +0100 Subject: [PATCH 18/18] fix: fix in update organization test --- .../organisations/tests/organisations.service.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/organisations/tests/organisations.service.spec.ts b/src/modules/organisations/tests/organisations.service.spec.ts index cd3df9547..93cc8a77c 100644 --- a/src/modules/organisations/tests/organisations.service.spec.ts +++ b/src/modules/organisations/tests/organisations.service.spec.ts @@ -101,7 +101,7 @@ describe('OrganisationsService', () => { .spyOn(organisationRepository, 'findOneBy') .mockResolvedValueOnce({ ...organisation, ...updateOrganisationDto }); - const result = await service.update(id, updateOrganisationDto); + const result = await service.updateOrganisation(id, updateOrganisationDto); expect(result).toEqual({ message: 'Organisation successfully updated', @@ -115,7 +115,7 @@ describe('OrganisationsService', () => { jest.spyOn(organisationRepository, 'findOneBy').mockResolvedValueOnce(null); - await expect(service.update(id, updateOrganisationDto)).rejects.toThrow(NotFoundException); + await expect(service.updateOrganisation(id, updateOrganisationDto)).rejects.toThrow(NotFoundException); }); it('should throw InternalServerErrorException if an unexpected error occurs', async () => { @@ -124,7 +124,7 @@ describe('OrganisationsService', () => { jest.spyOn(organisationRepository, 'findOneBy').mockRejectedValueOnce(new Error('Unexpected error')); - await expect(service.update(id, updateOrganisationDto)).rejects.toThrow(InternalServerErrorException); + await expect(service.updateOrganisation(id, updateOrganisationDto)).rejects.toThrow(InternalServerErrorException); }); }); });