diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e0059774..1df59fe5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,57 +2,92 @@ Hello! We're glad to have you interested in contributing to Note Block World. This document will guide you through the process of setting up the project and submitting your contributions. -This page is a work-in-progress and will be updated as the project evolves. If you have any questions or need help, feel free to reach out to us on our [Discord server](https://discord.gg/note-block-world-608692895179997252). +This page is a work in progress and will be updated as the project evolves. If you have any questions or need help, feel free to reach out to us on our [Discord server](https://discord.gg/note-block-world-608692895179997252). ## Stack This is a multipackage monorepo managed by [pnpm](https://pnpm.io/), which houses both the backend and frontend of the website. The backend is built with [NestJS](https://nestjs.com/) and the frontend is built with [Next.js](https://nextjs.org/) using server-side rendering (SSR). We use [MongoDB](https://www.mongodb.com/) as our database and [Backblaze B2](https://www.backblaze.com/cloud-storage) for file storage via its S3-compatible API. -## Setting up +## Setting up the project for development -To easily install the project, you can use the [docker-compose.yml](docker-compose.yml) file. +To easily install the project, you can use the [docker-compose-dev.yml](docker-compose-dev.yml) file. ```bash -docker-compose up -d +docker-compose -f docker-compose-dev.yml up -d ``` +obs: You can remove the `-d` flag to see the containers' logs. -This will start the backend and frontend servers, as well as a MongoDB instance. +This will start a database container, a maildev container, a minio container, and a minio-client container. +You can check the authentication details in the [docker-compose-dev.yml](docker-compose-dev.yml) file. ---- +To configure the env variables, create `.env.development` and `.env.local` files in the [backend](server) and [front-end](web) packages, based on the example files provided. Alternatively, set the environment variables directly in your shell like so: -Alternatively, you can install and use `pnpm` directly: +### backend: ```bash -npm i -g pnpm -pnpm install -``` +export NODE_ENV=development -To configure the env variables, create `.env.development` and `.env.local` files in the backend and front-end packages, based on the example files provided. Alternatively, set the environment variables directly in your shell: +export GITHUB_CLIENT_ID=UNSET +export GITHUB_CLIENT_SECRET=UNSET -```bash -export JWT_SECRET="jwtsecret" -export JWT_EXPIRES_IN="1d" -export DB_HOST="localhost:27017" -export DB_PASSWORD="noteblockworldpassword" -export DB_USER="noteblockworlduser" -export SERVER_URL="http://localhost:3000" +export GOOGLE_CLIENT_ID=UNSET +export GOOGLE_CLIENT_SECRET=UNSET + +export DISCORD_CLIENT_ID=UNSET +export DISCORD_CLIENT_SECRET=UNSET + +export MAGIC_LINK_SECRET=development_magic_link_secret + +# in seconds +export COOKIE_EXPIRES_IN=604800 # 1 week + +export JWT_SECRET=developmentsecret +export JWT_EXPIRES_IN=1h + +export JWT_REFRESH_SECRET=developmentrefreshsecret +export JWT_REFRESH_EXPIRES_IN=7d + +export MONGO_URL=mongodb://noteblockworlduser:noteblockworldpassword@localhost:27017/noteblockworld?authSource=admin + +export SERVER_URL=http://localhost:4000 +export FRONTEND_URL=http://localhost:3000 +#APP_DOMAIN= + +export RECAPTCHA_KEY=disabled + +export S3_ENDPOINT=http://localhost:9000 +export S3_BUCKET_SONGS=noteblockworld-songs +export S3_BUCKET_THUMBS=noteblockworld-thumbs +export S3_KEY=minioadmin +export S3_SECRET=minioadmin +export S3_REGION=us-east-1 + +export WHITELISTED_USERS= + +export DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/UNSET + +export MAIL_TRANSPORT=smtp://user:pass@localhost:1025 +export MAIL_FROM="Example " ``` -You can generate a JWT secret with OpenSSL and replace `jwtsecret` with the output: +Note that for the OAuth providers, you will need to create an application on their respective developer portals and replace `UNSET` , in development, you can use the magic link login method for easy testing. + +### frontend: ```bash -openssl rand -hex 64 +export THUMBNAIL_URL=localhost:9000 +export NEXT_PUBLIC_RECAPTCHA_SITE_KEY="6Le7JNEpAAAAAN7US0WVtz10Mb-IfnTgw-IvEC6s" +export NEXT_PUBLIC_URL=http://localhost:3000 +export NEXT_PUBLIC_API_URL=http://localhost:4000/api/v1 ``` -In Windows, you can use `set` instead of `export`: -```bash -set JWT_SECRET="jwtsecret" -set JWT_EXPIRES_IN="1d" -set DB_HOST="mongodb://localhost:27017/noteblockworld" -set DB_PASSWORD="noteblockworldpassword" -set DB_USER="noteblockworlduser" -set SERVER_URL="http://localhost:3000" +In Windows, you can use `set` instead of `export`. +```cmd +set THUMBNAIL_URL=localhost:9000 +set NEXT_PUBLIC_RECAPTCHA_SITE_KEY="6Le7JNEpAAAAAN7US0WVtz10Mb-IfnTgw-IvEC6s" +set NEXT_PUBLIC_URL=http://localhost:3000 +set NEXT_PUBLIC_API_URL=http://localhost:4000/api/v1 ``` Finally, to run the frontend and backend servers: @@ -61,4 +96,40 @@ Finally, to run the frontend and backend servers: pnpm run dev ``` +If you only want to run the backend or frontend, you can use the following commands: + +```bash +pnpm run dev:server +``` + +```bash +pnpm run dev:web +``` + The backend server will be available at [http://localhost:3000](http://localhost:3000) and the frontend server will be available at [http://localhost:4000](http://localhost:4000). + + +For populating the database with some test data by sending a post request: + +```bash +curl -X 'GET' \ + 'http://localhost:4000/api/v1/seed/seed-dev' \ + -H 'accept: */*' +``` + +Just so you know, the seed route is only available in development mode. + +Currently, tests are only available for the [backend](server), and [shared](shared) packages. + +We use [Jest](https://jestjs.io/) for testing. To run the tests, you can use the following command on the package you want to test: + +```bash +pnpm test +``` + +## Code style + +We provide a [Prettier](https://prettier.io/) and [ESLint](https://eslint.org/) configuration for the project. You can run the following command to format your code: +```bash +pnpm run lint +``` diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 2b37768a..ad7e6cc1 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -1,6 +1,6 @@ -version: '3.8' services: mongodb: + container_name: opennoteworld-mongodb-dev image: mongo volumes: - mongodb_data:/data/db @@ -11,5 +11,54 @@ services: - MONGO_INITDB_DATABASE=noteblockworld - MONGO_INITDB_ROOT_USERNAME=noteblockworlduser + maildev: + container_name: opennoteworld-maildev-dev + image: maildev/maildev + ports: + - '1080:1080' # Web Interface + - '1025:1025' # SMTP Server + # to use the maildev container, you need to set the following environment variables in your application: + # MAIL_TRANSPORT=smtp://maildev:1025 or MAIL_TRANSPORT=smtp://localhost:1025 + # MAIL_FROM="Example " + # + # You can also use the maildev web interface to view sent emails at http://localhost:1080 + + minio: + container_name: minio + image: minio/minio + command: server /data --console-address :9001 + ports: + - "9000:9000" + - "9001:9001" + environment: + - MINIO_ROOT_USER=minioadmin + - MINIO_ROOT_PASSWORD=minioadmin + volumes: + - minio_data:/data + # You can access the MinIO web interface at http://localhost:9000 + # You can access the MinIO console at http://localhost:9001 + + mc: + container_name: minio-client + image: minio/mc + entrypoint: ['/bin/sh', '-c'] + depends_on: + - minio + environment: + - MINIO_ROOT_USER=minioadmin + - MINIO_ROOT_PASSWORD=minioadmin + command: > + -c ' + while ! mc alias set minio http://minio:9000 minioadmin minioadmin; do + echo "Waiting for MinIO to be available..." + sleep 2 + done && + mc mb minio/noteblockworld-songs && + mc mb minio/noteblockworld-thumbs && + mc policy set public minio/noteblockworld-songs && + mc policy set public minio/noteblockworld-thumbs + ' + volumes: mongodb_data: + minio_data: diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 53ce1134..22a51f29 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,6 +54,9 @@ importers: '@encode42/nbs.js': specifier: ^5.0.2 version: 5.0.2 + '@nestjs-modules/mailer': + specifier: ^2.0.2 + version: 2.0.2(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.1.14)(rxjs@7.8.1))(nodemailer@6.9.16) '@nestjs/common': specifier: ^10.4.15 version: 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1) @@ -78,6 +81,9 @@ importers: '@nestjs/swagger': specifier: ^7.4.2 version: 7.4.2(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.1.14)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14) + '@nestjs/throttler': + specifier: ^6.3.0 + version: 6.3.0(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.1.14)(rxjs@7.8.1))(reflect-metadata@0.1.14) '@types/uuid': specifier: ^9.0.8 version: 9.0.8 @@ -123,6 +129,9 @@ importers: passport-local: specifier: ^1.0.0 version: 1.0.0 + passport-magic-login: + specifier: ^1.2.2 + version: 1.2.2 passport-oauth2: specifier: ^1.8.0 version: 1.8.0 @@ -142,6 +151,9 @@ importers: specifier: ^3.4.0 version: 3.4.0(zod@3.24.1) devDependencies: + '@faker-js/faker': + specifier: ^9.3.0 + version: 9.3.0 '@nestjs/cli': specifier: ^10.4.9 version: 10.4.9 @@ -813,6 +825,70 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@css-inline/css-inline-android-arm-eabi@0.14.1': + resolution: {integrity: sha512-LNUR8TY4ldfYi0mi/d4UNuHJ+3o8yLQH9r2Nt6i4qeg1i7xswfL3n/LDLRXvGjBYqeEYNlhlBQzbPwMX1qrU6A==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@css-inline/css-inline-android-arm64@0.14.1': + resolution: {integrity: sha512-tH5us0NYGoTNBHOUHVV7j9KfJ4DtFOeTLA3cM0XNoMtArNu2pmaaBMFJPqECzavfXkLc7x5Z22UPZYjoyHfvCA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@css-inline/css-inline-darwin-arm64@0.14.1': + resolution: {integrity: sha512-QE5W1YRIfRayFrtrcK/wqEaxNaqLULPI0gZB4ArbFRd3d56IycvgBasDTHPre5qL2cXCO3VyPx+80XyHOaVkag==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@css-inline/css-inline-darwin-x64@0.14.1': + resolution: {integrity: sha512-mAvv2sN8awNFsbvBzlFkZPbCNZ6GCWY5/YcIz7V5dPYw+bHHRbjnlkNTEZq5BsDxErVrMIGvz05PGgzuNvZvdQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@css-inline/css-inline-linux-arm-gnueabihf@0.14.1': + resolution: {integrity: sha512-AWC44xL0X7BgKvrWEqfSqkT2tJA5kwSGrAGT+m0gt11wnTYySvQ6YpX0fTY9i3ppYGu4bEdXFjyK2uY1DTQMHA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@css-inline/css-inline-linux-arm64-gnu@0.14.1': + resolution: {integrity: sha512-drj0ciiJgdP3xKXvNAt4W+FH4KKMs8vB5iKLJ3HcH07sNZj58Sx++2GxFRS1el3p+GFp9OoYA6dgouJsGEqt0Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@css-inline/css-inline-linux-arm64-musl@0.14.1': + resolution: {integrity: sha512-FzknI+st8eA8YQSdEJU9ykcM0LZjjigBuynVF5/p7hiMm9OMP8aNhWbhZ8LKJpKbZrQsxSGS4g9Vnr6n6FiSdQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@css-inline/css-inline-linux-x64-gnu@0.14.1': + resolution: {integrity: sha512-yubbEye+daDY/4vXnyASAxH88s256pPati1DfVoZpU1V0+KP0BZ1dByZOU1ktExurbPH3gZOWisAnBE9xon0Uw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@css-inline/css-inline-linux-x64-musl@0.14.1': + resolution: {integrity: sha512-6CRAZzoy1dMLPC/tns2rTt1ZwPo0nL/jYBEIAsYTCWhfAnNnpoLKVh5Nm+fSU3OOwTTqU87UkGrFJhObD/wobQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@css-inline/css-inline-win32-x64-msvc@0.14.1': + resolution: {integrity: sha512-nzotGiaiuiQW78EzsiwsHZXbxEt6DiMUFcDJ6dhiliomXxnlaPyBfZb6/FMBgRJOf6sknDt/5695OttNmbMYzg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@css-inline/css-inline@0.14.1': + resolution: {integrity: sha512-u4eku+hnPqqHIGq/ZUQcaP0TrCbYeLIYBaK7qClNRGZbnh8RC4gVxLEIo8Pceo1nOK9E5G4Lxzlw5KnXcvflfA==} + engines: {node: '>= 10'} + '@emnapi/runtime@1.2.0': resolution: {integrity: sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==} @@ -839,6 +915,10 @@ packages: resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@faker-js/faker@9.3.0': + resolution: {integrity: sha512-r0tJ3ZOkMd9xsu3VRfqlFR6cz0V/jFYRswAIpC+m/DIfAUXq7g8N7wTAlhSANySXYGKzGryfDXwtwsY8TxEIDw==} + engines: {node: '>=18.0.0', npm: '>=9.0.0'} + '@floating-ui/core@1.6.2': resolution: {integrity: sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg==} @@ -1212,6 +1292,13 @@ packages: resolution: {integrity: sha512-XsEZi97+kKykmAiPpY+IpZoHxJY1srqFZp8jDt1/RySzC0kB0iZYt/VMIFqQKpLCARZjD7SOAz2AULtwYlesCA==} engines: {node: '>= 10'} + '@nestjs-modules/mailer@2.0.2': + resolution: {integrity: sha512-+z4mADQasg0H1ZaGu4zZTuKv2pu+XdErqx99PLFPzCDNTN/q9U59WPgkxVaHnsvKHNopLj5Xap7G4ZpptduoYw==} + peerDependencies: + '@nestjs/common': '>=7.0.9' + '@nestjs/core': '>=7.0.9' + nodemailer: '>=6.4.6' + '@nestjs/cli@10.4.9': resolution: {integrity: sha512-s8qYd97bggqeK7Op3iD49X2MpFtW4LVNLAwXFkfbRxKME6IYT7X0muNTJ2+QfI8hpbNx9isWkrLWIp+g5FOhiA==} engines: {node: '>= 16.14'} @@ -1351,6 +1438,13 @@ packages: '@nestjs/platform-express': optional: true + '@nestjs/throttler@6.3.0': + resolution: {integrity: sha512-IqTMbl5Iyxjts7NwbVriDND0Cnr8rwNqAPpF5HJE+UV+2VrVUBwCfDXKEiXu47vzzaQLlWPYegBsGO9OXxa+oQ==} + peerDependencies: + '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 + reflect-metadata: ^0.1.13 || ^0.2.0 + '@next/env@14.2.5': resolution: {integrity: sha512-/zZGkrTOsraVfYjGP8uM0p6r0BDT6xWpkjdVbcz66PJVSpwXX3yNiRycxAuDfBKGWBrZBXRuK/YVlkNgxHGwmA==} @@ -1469,6 +1563,9 @@ packages: engines: {node: '>=8.0.0', npm: '>=5.0.0'} hasBin: true + '@one-ini/wasm@0.1.1': + resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1775,6 +1872,9 @@ packages: '@rushstack/eslint-patch@1.10.3': resolution: {integrity: sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==} + '@selderee/plugin-htmlparser2@0.11.0': + resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==} + '@shrutibalasa/tailwind-grid-auto-fit@1.1.0': resolution: {integrity: sha512-sh/Vmdz/xTVziUF9ZYDb5YpECApdK85KXSRIJY58GzmdUuum5QBE3jqDeNMjICEVaVOSrOnp+gqUWx8A9dmZjA==} @@ -2053,6 +2153,9 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/ejs@3.1.5': + resolution: {integrity: sha512-nv+GSx77ZtXiJzwKdsASqi+YQ5Z7vwHsTP0JY2SiQgjGckkBRKZnk8nIM+7oUZ1VCtuTz0+By4qVR7fqzp/Dfg==} + '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -2128,6 +2231,12 @@ packages: '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + '@types/mjml-core@4.15.1': + resolution: {integrity: sha512-qu8dUksU8yXX18qMTFINkM4uoz7WQYC5F14lcWeSNmWbulaGG0KG19yeZwpx75b9RJXr8WI/FRHH0LyQTU9JbA==} + + '@types/mjml@4.7.4': + resolution: {integrity: sha512-vyi1vzWgMzFMwZY7GSZYX0GU0dmtC8vLHwpgk+NWmwbwRSrlieVyJ9sn5elodwUfklJM7yGl0zQeet1brKTWaQ==} + '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} @@ -2137,6 +2246,9 @@ packages: '@types/multer@1.4.12': resolution: {integrity: sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==} + '@types/node@14.18.63': + resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} + '@types/node@20.14.2': resolution: {integrity: sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==} @@ -2170,6 +2282,9 @@ packages: '@types/prop-types@15.7.12': resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} + '@types/pug@2.0.10': + resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==} + '@types/qs@6.9.15': resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==} @@ -2365,6 +2480,11 @@ packages: resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} engines: {node: '>=0.4.0'} + acorn@7.4.1: + resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} + engines: {node: '>=0.4.0'} + hasBin: true + acorn@8.11.3: resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} engines: {node: '>=0.4.0'} @@ -2402,6 +2522,10 @@ packages: ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + alce@1.2.0: + resolution: {integrity: sha512-XppPf2S42nO2WhvKzlwzlfcApcXHzjlod30pKmcWjRgLOtqoe5DMuqdiYoM6AgyXksc6A6pV4v1L/WW217e57w==} + engines: {node: '>=0.8.0'} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -2511,6 +2635,9 @@ packages: asap@2.0.6: resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + assert-never@1.4.0: + resolution: {integrity: sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA==} + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} @@ -2577,6 +2704,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + babel-walk@3.0.0-canary-5: + resolution: {integrity: sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==} + engines: {node: '>= 10.0.0'} + bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} @@ -2608,6 +2739,9 @@ packages: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + bowser@2.11.0: resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} @@ -2679,6 +2813,9 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + camel-case@3.0.0: + resolution: {integrity: sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==} + camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} @@ -2704,6 +2841,10 @@ packages: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} + chalk@3.0.0: + resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==} + engines: {node: '>=8'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2731,6 +2872,9 @@ packages: character-entities@2.0.2: resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + character-parser@2.2.0: + resolution: {integrity: sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==} + character-reference-invalid@1.1.4: resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} @@ -2740,6 +2884,13 @@ packages: chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + cheerio-select@2.1.0: + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + + cheerio@1.0.0-rc.12: + resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} + engines: {node: '>= 6'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -2768,6 +2919,10 @@ packages: class-variance-authority@0.7.0: resolution: {integrity: sha512-jFI8IQw4hczaL4ALINxqLEXQbWcNjoSkloa4IaufXCJr6QawJyw7tuRysRsrE8w2p/4gGaxKIt/hX3qz/IbD1A==} + clean-css@4.2.4: + resolution: {integrity: sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==} + engines: {node: '>= 4.0'} + cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -2850,6 +3005,10 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -2857,6 +3016,10 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + commander@6.2.1: + resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} + engines: {node: '>= 6'} + comment-json@4.2.5: resolution: {integrity: sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==} engines: {node: '>= 6'} @@ -2875,9 +3038,15 @@ packages: resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} engines: {'0': node >= 6.0} + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + consola@2.15.3: resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} + constantinople@4.0.1: + resolution: {integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==} + content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -2927,6 +3096,10 @@ packages: create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + cross-spawn@6.0.6: + resolution: {integrity: sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==} + engines: {node: '>=4.8'} + cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -2935,6 +3108,13 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + + css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -3003,6 +3183,10 @@ packages: babel-plugin-macros: optional: true + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -3037,6 +3221,10 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + detect-libc@2.0.3: resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} engines: {node: '>=8'} @@ -3048,6 +3236,9 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -3073,6 +3264,10 @@ packages: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + display-notification@2.0.0: + resolution: {integrity: sha512-TdmtlAcdqy1NU+j7zlkDdMnCL878zriLaBmoD9quOoq1ySSSGv03l0hXK5CvIFZlIfFI/hizqdQuW+Num7xuhw==} + engines: {node: '>=4'} + dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} @@ -3084,6 +3279,36 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + doctypes@1.1.0: + resolution: {integrity: sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==} + + dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@3.3.0: + resolution: {integrity: sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==} + engines: {node: '>= 4'} + + domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + + domutils@3.2.1: + resolution: {integrity: sha512-xWXmuRnN9OMP6ptPd2+H0cCbcYBULa5YDTbMm/2lvkWvNA3O4wcW+GvzooqBuNM8yy6pl3VIAeJTUUWUbfI5Fw==} + dotenv-expand@10.0.0: resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} engines: {node: '>=12'} @@ -3102,6 +3327,11 @@ packages: ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + editorconfig@1.0.4: + resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} + engines: {node: '>=14'} + hasBin: true + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -3150,6 +3380,10 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} + encoding-japanese@2.2.0: + resolution: {integrity: sha512-EuJWwlHPZ1LbADuKTClvHtwbaFn4rOD+dRAbWysqEOXRc2Uui0hJInNJrsdH0c+OhJA4nrCBdSkW4DD5YxAo6A==} + engines: {node: '>=8.10.0'} + enhanced-resolve@5.17.0: resolution: {integrity: sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==} engines: {node: '>=10.13.0'} @@ -3158,6 +3392,13 @@ packages: resolution: {integrity: sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==} engines: {node: '>=10.13.0'} + entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} @@ -3184,8 +3425,8 @@ packages: resolution: {integrity: sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==} engines: {node: '>= 0.4'} - es-module-lexer@1.6.0: - resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} + es-module-lexer@1.5.4: + resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} es-object-atoms@1.0.0: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} @@ -3210,9 +3451,17 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-goat@3.0.0: + resolution: {integrity: sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==} + engines: {node: '>=10'} + escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-applescript@1.0.0: + resolution: {integrity: sha512-4/hFwoYaC6TkpDn9A3pTC52zQPArFeXuIfhUtCGYdauTzXVP9H3BDr3oO/QzQehMpLDC7srvYgfwvImPFGfvBA==} + engines: {node: '>=0.10.0'} + escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} @@ -3367,6 +3616,11 @@ packages: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + esprima@1.2.5: + resolution: {integrity: sha512-S9VbPDU0adFErpDai3qDkjq8+G05ONtKzcyNrPKg/ZKa+tf879nX2KexNU95b31UoTJjRLInNBHHHjFPoCd7lQ==} + engines: {node: '>=0.4.0'} + hasBin: true + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} @@ -3380,6 +3634,10 @@ packages: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} + estraverse@1.9.3: + resolution: {integrity: sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==} + engines: {node: '>=0.10.0'} + estraverse@4.3.0: resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} engines: {node: '>=4.0'} @@ -3418,6 +3676,10 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + execa@0.10.0: + resolution: {integrity: sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==} + engines: {node: '>=4'} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -3438,6 +3700,9 @@ packages: resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} + extend-object@1.0.0: + resolution: {integrity: sha512-0dHDIXC7y7LDmCh/lp1oYkmv73K25AMugQI07r8eFopkW6f7Ufn1q+ETMsJjnV9Am14SlElkqy3O92r6xEaxPw==} + extend-shallow@2.0.1: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} engines: {node: '>=0.10.0'} @@ -3516,6 +3781,10 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + fixpack@4.0.0: + resolution: {integrity: sha512-5SM1+H2CcuJ3gGEwTiVo/+nd/hYpNj9Ch3iMDOQ58ndY+VGQ2QdvaUTkd3otjZvYnd/8LF/HkJ5cx7PBq0orCQ==} + hasBin: true + flat-cache@3.2.0: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} engines: {node: ^10.12.0 || >=12.0.0} @@ -3630,6 +3899,14 @@ packages: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} + get-port@5.1.1: + resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} + engines: {node: '>=8'} + + get-stream@3.0.0: + resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==} + engines: {node: '>=4'} + get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} @@ -3652,6 +3929,11 @@ packages: glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + glob@10.3.12: + resolution: {integrity: sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + glob@10.4.1: resolution: {integrity: sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==} engines: {node: '>=16 || 14 >=14.18'} @@ -3707,6 +3989,11 @@ packages: resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} engines: {node: '>=6.0'} + handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} @@ -3754,6 +4041,10 @@ packages: hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + hexoid@1.0.0: resolution: {integrity: sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==} engines: {node: '>=8'} @@ -3765,9 +4056,27 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-minifier@4.0.0: + resolution: {integrity: sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==} + engines: {node: '>=6'} + hasBin: true + + html-to-text@9.0.5: + resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==} + engines: {node: '>=14'} + html-url-attributes@3.0.0: resolution: {integrity: sha512-/sXbVCWayk6GDVg3ctOX6nxaVj7So40FcFAnWlWGNAB1LpYKcV5Cd10APjPjW80O7zYW2MsjBV4zZ7IZO5fVow==} + htmlparser2@5.0.1: + resolution: {integrity: sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==} + + htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + + htmlparser2@9.1.0: + resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==} + http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -3784,6 +4093,10 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -3817,6 +4130,9 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + ini@4.1.3: resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -3908,9 +4224,17 @@ packages: is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + is-empty@1.2.0: resolution: {integrity: sha512-F2FnH/otLNJv0J6wc73A5Xo7oHLNnqplYqZhUu01tD54DIPvxIRSTSLkrUB/M0nHO4vo1O9PDfN4KoTxCzLh/w==} + is-expression@4.0.0: + resolution: {integrity: sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==} + is-extendable@0.1.1: resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} engines: {node: '>=0.10.0'} @@ -3972,6 +4296,9 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-promise@2.2.2: + resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} + is-reference@3.0.2: resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} @@ -3987,6 +4314,10 @@ packages: resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} engines: {node: '>= 0.4'} + is-stream@1.1.0: + resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} + engines: {node: '>=0.10.0'} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -4018,6 +4349,10 @@ packages: resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} engines: {node: '>= 0.4'} + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} @@ -4062,6 +4397,10 @@ packages: iterator.prototype@1.1.2: resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} + jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + jackspeak@3.4.0: resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==} engines: {node: '>=14'} @@ -4211,9 +4550,21 @@ packages: resolution: {integrity: sha512-uy2bNX5zQ+tESe+TiC7ilGRz8AtRGmnJH55NC5S0nSUjvvvM2hJHmefHErugGXN4pNv4Qx7vLsnNw9qJ9mtIsw==} hasBin: true + js-beautify@1.15.1: + resolution: {integrity: sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==} + engines: {node: '>=14'} + hasBin: true + js-confetti@0.12.0: resolution: {integrity: sha512-1R0Akxn3Zn82pMqW65N1V2NwKkZJ75bvBN/VAb36Ya0YHwbaSiAJZVRr/19HBxH/O8x2x01UFAbYI18VqlDN6g==} + js-cookie@3.0.5: + resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} + engines: {node: '>=14'} + + js-stringify@1.0.2: + resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -4274,6 +4625,9 @@ packages: resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} engines: {node: '>=12', npm: '>=6'} + jstransformer@1.0.0: + resolution: {integrity: sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -4281,6 +4635,11 @@ packages: jszip@3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + juice@10.0.1: + resolution: {integrity: sha512-ZhJT1soxJCkOiO55/mz8yeBKTAJhRzX9WBO+16ZTqNTONnnVlUPyVBIzQ7lDRjaBdTbid+bAnyIon/GM3yp4cA==} + engines: {node: '>=10.0.0'} + hasBin: true + jwa@1.4.1: resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} @@ -4313,6 +4672,9 @@ packages: resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} engines: {node: '>=0.10'} + leac@0.6.0: + resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==} + leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -4321,9 +4683,18 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + libbase64@1.3.0: + resolution: {integrity: sha512-GgOXd0Eo6phYgh0DJtjQ2tO8dc0IVINtZJeARPeiIJqge+HdsWSuaDTe8ztQ7j/cONByDZ3zeB325AHiv5O0dg==} + + libmime@5.3.6: + resolution: {integrity: sha512-j9mBC7eiqi6fgBPAGvKCXJKJSIASanYF4EeA4iBzSG0HxQxmXnR3KbyWqTn4CwsKSebqCv2f5XZfAO6sKzgvwA==} + libphonenumber-js@1.11.3: resolution: {integrity: sha512-RU0CTsLCu2v6VEzdP+W6UU2n5+jEpMDRkGxUeBgsAJgre3vKgm17eApISH9OQY4G0jZYJVIc8qXmz6CJFueAFg==} + libqp@2.1.1: + resolution: {integrity: sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow==} + lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} @@ -4342,6 +4713,14 @@ packages: resolution: {integrity: sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + linkify-it@5.0.0: + resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==} + + liquidjs@10.20.0: + resolution: {integrity: sha512-y1tHDaHGtYXcZehRzy8UST0t0E9iPOdT4hAk5ZuSquaDRW4j1lGUePaVyX6AP07cZarm3tL8FEtHx19UXgi9pQ==} + engines: {node: '>=14'} + hasBin: true + load-plugin@6.0.3: resolution: {integrity: sha512-kc0X2FEUZr145odl68frm+lMJuQ23+rTXYmR6TImqPtbpmXC4vVXbWKDQ9IzndA0HfyQamWfKLhzsqGSTxE63w==} @@ -4398,6 +4777,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lower-case@1.1.4: + resolution: {integrity: sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==} + lru-cache@10.2.2: resolution: {integrity: sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==} engines: {node: 14 || >=16.14} @@ -4409,6 +4791,12 @@ packages: resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} engines: {node: '>=12'} + mailparser@3.7.2: + resolution: {integrity: sha512-iI0p2TCcIodR1qGiRoDBBwboSSff50vQAWytM5JRggLfABa4hHYCf3YVujtuzV454xrOP352VsAPIzviqMTo4Q==} + + mailsplit@5.4.2: + resolution: {integrity: sha512-4cczG/3Iu3pyl8JgQ76dKkisurZTmxMrA4dj/e8d2jKYcFTZ7MxOzg1gTioTDMPuFXwTrVuN/gxhkrO7wLg7qA==} + make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} @@ -4471,6 +4859,9 @@ packages: memory-pager@1.5.0: resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} + mensch@0.3.4: + resolution: {integrity: sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==} + merge-descriptors@1.0.1: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} @@ -4612,6 +5003,10 @@ packages: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} + minimatch@9.0.1: + resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==} + engines: {node: '>=16 || 14 >=14.17'} + minimatch@9.0.4: resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} engines: {node: '>=16 || 14 >=14.17'} @@ -4627,6 +5022,105 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + mjml-accordion@4.15.3: + resolution: {integrity: sha512-LPNVSj1LyUVYT9G1gWwSw3GSuDzDsQCu0tPB2uDsq4VesYNnU6v3iLCQidMiR6azmIt13OEozG700ygAUuA6Ng==} + + mjml-body@4.15.3: + resolution: {integrity: sha512-7pfUOVPtmb0wC+oUOn4xBsAw4eT5DyD6xqaxj/kssu6RrFXOXgJaVnDPAI9AzIvXJ/5as9QrqRGYAddehwWpHQ==} + + mjml-button@4.15.3: + resolution: {integrity: sha512-79qwn9AgdGjJR1vLnrcm2rq2AsAZkKC5JPwffTMG+Nja6zGYpTDZFZ56ekHWr/r1b5WxkukcPj2PdevUug8c+Q==} + + mjml-carousel@4.15.3: + resolution: {integrity: sha512-3ju6I4l7uUhPRrJfN3yK9AMsfHvrYbRkcJ1GRphFHzUj37B2J6qJOQUpzA547Y4aeh69TSb7HFVf1t12ejQxVw==} + + mjml-cli@4.15.3: + resolution: {integrity: sha512-+V2TDw3tXUVEptFvLSerz125C2ogYl8klIBRY1m5BHd4JvGVf3yhx8N3PngByCzA6PGcv/eydGQN+wy34SHf0Q==} + hasBin: true + + mjml-column@4.15.3: + resolution: {integrity: sha512-hYdEFdJGHPbZJSEysykrevEbB07yhJGSwfDZEYDSbhQQFjV2tXrEgYcFD5EneMaowjb55e3divSJxU4c5q4Qgw==} + + mjml-core@4.15.3: + resolution: {integrity: sha512-Dmwk+2cgSD9L9GmTbEUNd8QxkTZtW9P7FN/ROZW/fGZD6Hq6/4TB0zEspg2Ow9eYjZXO2ofOJ3PaQEEShKV0kQ==} + + mjml-divider@4.15.3: + resolution: {integrity: sha512-vh27LQ9FG/01y0b9ntfqm+GT5AjJnDSDY9hilss2ixIUh0FemvfGRfsGVeV5UBVPBKK7Ffhvfqc7Rciob9Spzw==} + + mjml-group@4.15.3: + resolution: {integrity: sha512-HSu/rKnGZVKFq3ciT46vi1EOy+9mkB0HewO4+P6dP/Y0UerWkN6S3UK11Cxsj0cAp0vFwkPDCdOeEzRdpFEkzA==} + + mjml-head-attributes@4.15.3: + resolution: {integrity: sha512-2ISo0r5ZKwkrvJgDou9xVPxxtXMaETe2AsAA02L89LnbB2KC0N5myNsHV0sEysTw9+CfCmgjAb0GAI5QGpxKkQ==} + + mjml-head-breakpoint@4.15.3: + resolution: {integrity: sha512-Eo56FA5C2v6ucmWQL/JBJ2z641pLOom4k0wP6CMZI2utfyiJ+e2Uuinj1KTrgDcEvW4EtU9HrfAqLK9UosLZlg==} + + mjml-head-font@4.15.3: + resolution: {integrity: sha512-CzV2aDPpiNIIgGPHNcBhgyedKY4SX3BJoTwOobSwZVIlEA6TAWB4Z9WwFUmQqZOgo1AkkiTHPZQvGcEhFFXH6g==} + + mjml-head-html-attributes@4.15.3: + resolution: {integrity: sha512-MDNDPMBOgXUZYdxhosyrA2kudiGO8aogT0/cODyi2Ed9o/1S7W+je11JUYskQbncqhWKGxNyaP4VWa+6+vUC/g==} + + mjml-head-preview@4.15.3: + resolution: {integrity: sha512-J2PxCefUVeFwsAExhrKo4lwxDevc5aKj888HBl/wN4EuWOoOg06iOGCxz4Omd8dqyFsrqvbBuPqRzQ+VycGmaA==} + + mjml-head-style@4.15.3: + resolution: {integrity: sha512-9J+JuH+mKrQU65CaJ4KZegACUgNIlYmWQYx3VOBR/tyz+8kDYX7xBhKJCjQ1I4wj2Tvga3bykd89Oc2kFZ5WOw==} + + mjml-head-title@4.15.3: + resolution: {integrity: sha512-IM59xRtsxID4DubQ0iLmoCGXguEe+9BFG4z6y2xQDrscIa4QY3KlfqgKGT69ojW+AVbXXJPEVqrAi4/eCsLItQ==} + + mjml-head@4.15.3: + resolution: {integrity: sha512-o3mRuuP/MB5fZycjD3KH/uXsnaPl7Oo8GtdbJTKtH1+O/3pz8GzGMkscTKa97l03DAG2EhGrzzLcU2A6eshwFw==} + + mjml-hero@4.15.3: + resolution: {integrity: sha512-9cLAPuc69yiuzNrMZIN58j+HMK1UWPaq2i3/Fg2ZpimfcGFKRcPGCbEVh0v+Pb6/J0+kf8yIO0leH20opu3AyQ==} + + mjml-image@4.15.3: + resolution: {integrity: sha512-g1OhSdofIytE9qaOGdTPmRIp7JsCtgO0zbsn1Fk6wQh2gEL55Z40j/VoghslWAWTgT2OHFdBKnMvWtN6U5+d2Q==} + + mjml-migrate@4.15.3: + resolution: {integrity: sha512-sr/+35RdxZroNQVegjpfRHJ5hda9XCgaS4mK2FGO+Mb1IUevKfeEPII3F/cHDpNwFeYH3kAgyqQ22ClhGLWNBA==} + hasBin: true + + mjml-navbar@4.15.3: + resolution: {integrity: sha512-VsKH/Jdlf8Yu3y7GpzQV5n7JMdpqvZvTSpF6UQXL0PWOm7k6+LX+sCZimOfpHJ+wCaaybpxokjWZ71mxOoCWoA==} + + mjml-parser-xml@4.15.3: + resolution: {integrity: sha512-Tz0UX8/JVYICLjT+U8J1f/TFxIYVYjzZHeh4/Oyta0pLpRLeZlxEd71f3u3kdnulCKMP4i37pFRDmyLXAlEuLw==} + + mjml-preset-core@4.15.3: + resolution: {integrity: sha512-1zZS8P4O0KweWUqNS655+oNnVMPQ1Rq1GaZq5S9JfwT1Vh/m516lSmiTW9oko6gGHytt5s6Yj6oOeu5Zm8FoLw==} + + mjml-raw@4.15.3: + resolution: {integrity: sha512-IGyHheOYyRchBLiAEgw3UM11kFNmBSMupu2BDdejC6ZiDhEAdG+tyERlsCwDPYtXanvFpGWULIu3XlsUPc+RZw==} + + mjml-section@4.15.3: + resolution: {integrity: sha512-JfVPRXH++Hd933gmQfG8JXXCBCR6fIzC3DwiYycvanL/aW1cEQ2EnebUfQkt5QzlYjOkJEH+JpccAsq3ln6FZQ==} + + mjml-social@4.15.3: + resolution: {integrity: sha512-7sD5FXrESOxpT9Z4Oh36bS6u/geuUrMP1aCg2sjyAwbPcF1aWa2k9OcatQfpRf6pJEhUZ18y6/WBBXmMVmSzXg==} + + mjml-spacer@4.15.3: + resolution: {integrity: sha512-3B7Qj+17EgDdAtZ3NAdMyOwLTX1jfmJuY7gjyhS2HtcZAmppW+cxqHUBwCKfvSRgTQiccmEvtNxaQK+tfyrZqA==} + + mjml-table@4.15.3: + resolution: {integrity: sha512-FLx7DcRKTdKdcOCbMyBaeudeHaHpwPveRrBm6WyQe3LXx6FfdmOh59i71/16LFQMgBOD3N4/UJkzxLzlTJzMqQ==} + + mjml-text@4.15.3: + resolution: {integrity: sha512-+C0hxCmw9kg0XzT6vhE5mFkK6y225nC8UEQcN94K0fBCjPKkM+HqZMwGX205fzdGRi+Bxa55b/VhrIVwdv+8vw==} + + mjml-validator@4.15.3: + resolution: {integrity: sha512-Xb72KdqRwjv/qM2rJpV22syyP2N3cRQ9VVDrN6u2FSzLq02buFNxmSPJ7CKhat3PrUNdVHU75KZwOf/tz4UEhA==} + + mjml-wrapper@4.15.3: + resolution: {integrity: sha512-ditsCijeHJrmBmObtJmQ18ddLxv5oPyMTdPU8Di8APOnD2zPk7Z4UAuJSl7HXB45oFiivr3MJf4koFzMUSZ6Gg==} + + mjml@4.15.3: + resolution: {integrity: sha512-bW2WpJxm6HS+S3Yu6tq1DUPFoTxU9sPviUSmnL7Ua+oVO3WA5ILFWqvujUlz+oeuM+HCwEyMiP5xvKNPENVjYA==} + hasBin: true + mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -4752,6 +5246,12 @@ packages: react: '>= 16.0.0' react-dom: '>= 16.0.0' + nice-try@1.0.5: + resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} + + no-case@2.3.2: + resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==} + node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} @@ -4776,6 +5276,10 @@ packages: node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + nodemailer@6.9.16: + resolution: {integrity: sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==} + engines: {node: '>=6.0.0'} + nopt@7.2.1: resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -4809,6 +5313,10 @@ packages: resolution: {integrity: sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==} engines: {node: ^16.14.0 || >=18.0.0} + npm-run-path@2.0.2: + resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} + engines: {node: '>=4'} + npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} @@ -4890,6 +5398,9 @@ packages: nprogress@0.2.0: resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + oauth@0.10.0: resolution: {integrity: sha512-1orQ9MT1vHFGQxhuy7E/0gECD3fd2fCC+PIX+/jgmU/gI3EpRocXtmtvxCO5x3WZ443FLTLFWNDjl5MPJf9u+Q==} @@ -4947,6 +5458,10 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + open@7.4.2: + resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} + engines: {node: '>=8'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -4959,6 +5474,14 @@ packages: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} + p-event@4.2.0: + resolution: {integrity: sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==} + engines: {node: '>=8'} + + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -4975,16 +5498,27 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} + p-wait-for@3.2.0: + resolution: {integrity: sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA==} + engines: {node: '>=8'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + param-case@2.1.1: + resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -5003,6 +5537,15 @@ packages: resolution: {integrity: sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==} engines: {node: '>=16'} + parse5-htmlparser2-tree-adapter@7.1.0: + resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} + + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + + parseley@0.12.1: + resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==} + parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -5022,6 +5565,10 @@ packages: resolution: {integrity: sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==} engines: {node: '>= 0.4.0'} + passport-magic-login@1.2.2: + resolution: {integrity: sha512-KBzxei/qvt2UDkS1qXz2VR2XajbWB5808W+oDpeMsfGqMgBKicaDX7HOrgAVpvw2q+fWOuh21LWe+kYyZPogDA==} + engines: {node: '>=10'} + passport-oauth2@1.8.0: resolution: {integrity: sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==} engines: {node: '>= 0.4.0'} @@ -5042,6 +5589,10 @@ packages: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} + path-key@2.0.1: + resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} + engines: {node: '>=4'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -5072,6 +5623,9 @@ packages: pause@0.0.1: resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} + peberminta@0.9.0: + resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} + periscopic@3.1.0: resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} @@ -5171,6 +5725,10 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + preview-email@3.1.0: + resolution: {integrity: sha512-ZtV1YrwscEjlrUzYrTSs6Nwo49JM3pXLM4fFOBSC3wSni+bxaWlw9/Qgk75PZO8M7cX2EybmL2iwvaV3vkAttw==} + engines: {node: '>=14'} + proc-log@4.2.0: resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} @@ -5190,6 +5748,9 @@ packages: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} + promise@7.3.1: + resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} + prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -5200,6 +5761,9 @@ packages: property-information@6.5.0: resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -5207,23 +5771,63 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} + pug-attrs@3.0.0: + resolution: {integrity: sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==} - pure-rand@6.1.0: - resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + pug-code-gen@3.0.3: + resolution: {integrity: sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw==} - qs@6.11.0: - resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} - engines: {node: '>=0.6'} + pug-error@2.1.0: + resolution: {integrity: sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg==} - qs@6.13.0: - resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} - engines: {node: '>=0.6'} + pug-filters@4.0.0: + resolution: {integrity: sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A==} - qs@6.13.1: - resolution: {integrity: sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==} + pug-lexer@5.0.1: + resolution: {integrity: sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==} + + pug-linker@4.0.0: + resolution: {integrity: sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw==} + + pug-load@3.0.0: + resolution: {integrity: sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ==} + + pug-parser@6.0.0: + resolution: {integrity: sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw==} + + pug-runtime@3.0.1: + resolution: {integrity: sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg==} + + pug-strip-comments@2.0.0: + resolution: {integrity: sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ==} + + pug-walk@2.0.0: + resolution: {integrity: sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ==} + + pug@3.0.3: + resolution: {integrity: sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g==} + + punycode.js@2.3.1: + resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} + engines: {node: '>=6'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + qs@6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} + + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + + qs@6.13.1: + resolution: {integrity: sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==} engines: {node: '>=0.6'} queue-microtask@1.2.3: @@ -5240,6 +5844,10 @@ packages: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + react-dom@18.2.0: resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: @@ -5352,6 +5960,10 @@ packages: resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} engines: {node: '>= 0.4'} + relateurl@0.2.7: + resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} + engines: {node: '>= 0.10'} + remark-mdx@3.0.1: resolution: {integrity: sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==} @@ -5420,6 +6032,10 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + run-applescript@3.2.0: + resolution: {integrity: sha512-Ep0RsvAjnRcBX1p5vogbaBdAGu/8j/ewpvGqnQYunnLd9SM0vWcPJewPKNnWFggf0hF0pwIgwV5XK7qQ7UZ8Qg==} + engines: {node: '>=4'} + run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -5475,6 +6091,13 @@ packages: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} engines: {node: '>=4'} + selderee@0.11.0: + resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + hasBin: true + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -5526,10 +6149,18 @@ packages: resolution: {integrity: sha512-7i/dt5kGl7qR4gwPRD2biwD2/SvBn3O04J77XKFgL2OnZtQw+AG9wnuS/csmu80nPRHLYE9E41fyEiG8nhH6/Q==} engines: {libvips: '>=8.15.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} + shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} @@ -5574,6 +6205,9 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + slick@1.12.2: + resolution: {integrity: sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==} + smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -5696,10 +6330,18 @@ packages: resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} engines: {node: '>=8'} + strip-eof@1.0.0: + resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} + engines: {node: '>=0.10.0'} + strip-final-newline@2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -5836,6 +6478,10 @@ packages: through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + tlds@1.255.0: + resolution: {integrity: sha512-tcwMRIioTcF/FcxLev8MJWxCp+GUALRhFEqbDoZrnowmKSGqPrl5pqS+Sut2m8BgJ6S4FExCSSpGffZ0Tks6Aw==} + hasBin: true + tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} @@ -5855,6 +6501,9 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} + token-stream@1.0.0: + resolution: {integrity: sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg==} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -6023,6 +6672,14 @@ packages: engines: {node: '>=14.17'} hasBin: true + uc.micro@2.1.0: + resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + uid2@0.0.4: resolution: {integrity: sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==} @@ -6096,6 +6753,9 @@ packages: peerDependencies: browserslist: '>= 4.21.0' + upper-case@1.1.3: + resolution: {integrity: sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -6142,6 +6802,10 @@ packages: resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} engines: {node: '>=10.12.0'} + valid-data-url@3.0.1: + resolution: {integrity: sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==} + engines: {node: '>=10'} + validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -6172,6 +6836,10 @@ packages: vfile@6.0.1: resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} + void-elements@3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + walk-up-path@3.0.1: resolution: {integrity: sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==} @@ -6185,6 +6853,10 @@ packages: wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + web-resource-inliner@6.0.1: + resolution: {integrity: sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==} + engines: {node: '>=10.0.0'} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -6232,6 +6904,10 @@ packages: resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} engines: {node: '>= 0.4'} + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -6242,10 +6918,17 @@ packages: engines: {node: ^16.13.0 || >=18.0.0} hasBin: true + with@7.0.2: + resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==} + engines: {node: '>= 10.0.0'} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -6891,7 +7574,7 @@ snapshots: '@babel/core@7.24.7': dependencies: '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 '@babel/generator': 7.24.7 '@babel/helper-compilation-targets': 7.24.7 '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) @@ -6950,7 +7633,7 @@ snapshots: '@babel/helper-module-imports': 7.24.7 '@babel/helper-simple-access': 7.24.7 '@babel/helper-split-export-declaration': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 + '@babel/helper-validator-identifier': 7.25.9 transitivePeerDependencies: - supports-color @@ -7067,13 +7750,13 @@ snapshots: '@babel/template@7.24.7': dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 '@babel/parser': 7.24.7 '@babel/types': 7.24.7 '@babel/traverse@7.24.7': dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 '@babel/generator': 7.24.7 '@babel/helper-environment-visitor': 7.24.7 '@babel/helper-function-name': 7.24.7 @@ -7101,6 +7784,49 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@css-inline/css-inline-android-arm-eabi@0.14.1': + optional: true + + '@css-inline/css-inline-android-arm64@0.14.1': + optional: true + + '@css-inline/css-inline-darwin-arm64@0.14.1': + optional: true + + '@css-inline/css-inline-darwin-x64@0.14.1': + optional: true + + '@css-inline/css-inline-linux-arm-gnueabihf@0.14.1': + optional: true + + '@css-inline/css-inline-linux-arm64-gnu@0.14.1': + optional: true + + '@css-inline/css-inline-linux-arm64-musl@0.14.1': + optional: true + + '@css-inline/css-inline-linux-x64-gnu@0.14.1': + optional: true + + '@css-inline/css-inline-linux-x64-musl@0.14.1': + optional: true + + '@css-inline/css-inline-win32-x64-msvc@0.14.1': + optional: true + + '@css-inline/css-inline@0.14.1': + 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 + '@emnapi/runtime@1.2.0': dependencies: tslib: 2.8.1 @@ -7131,6 +7857,8 @@ snapshots: '@eslint/js@8.57.0': {} + '@faker-js/faker@9.3.0': {} + '@floating-ui/core@1.6.2': dependencies: '@floating-ui/utils': 0.2.2 @@ -7613,6 +8341,26 @@ snapshots: '@napi-rs/canvas-linux-x64-musl': 0.1.53 '@napi-rs/canvas-win32-x64-msvc': 0.1.53 + '@nestjs-modules/mailer@2.0.2(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.1.14)(rxjs@7.8.1))(nodemailer@6.9.16)': + dependencies: + '@css-inline/css-inline': 0.14.1 + '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1) + '@nestjs/core': 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.1.14)(rxjs@7.8.1) + glob: 10.3.12 + nodemailer: 6.9.16 + 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.20.0 + mjml: 4.15.3 + preview-email: 3.1.0 + pug: 3.0.3 + transitivePeerDependencies: + - encoding + '@nestjs/cli@10.4.9': dependencies: '@angular-devkit/core': 17.3.11(chokidar@3.6.0) @@ -7761,6 +8509,12 @@ snapshots: optionalDependencies: '@nestjs/platform-express': 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@10.4.15) + '@nestjs/throttler@6.3.0(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.1.14)(rxjs@7.8.1))(reflect-metadata@0.1.14)': + dependencies: + '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1) + '@nestjs/core': 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.1.14)(rxjs@7.8.1) + reflect-metadata: 0.1.14 + '@next/env@14.2.5': {} '@next/eslint-plugin-next@13.4.12': @@ -7879,6 +8633,9 @@ snapshots: transitivePeerDependencies: - encoding + '@one-ini/wasm@0.1.1': + optional: true + '@pkgjs/parseargs@0.11.0': optional: true @@ -8192,6 +8949,12 @@ snapshots: '@rushstack/eslint-patch@1.10.3': {} + '@selderee/plugin-htmlparser2@0.11.0': + dependencies: + domhandler: 5.0.3 + selderee: 0.11.0 + optional: true + '@shrutibalasa/tailwind-grid-auto-fit@1.1.0': {} '@sinclair/typebox@0.27.8': {} @@ -8606,6 +9369,9 @@ snapshots: dependencies: '@types/ms': 0.7.34 + '@types/ejs@3.1.5': + optional: true + '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 @@ -8696,6 +9462,14 @@ snapshots: '@types/mime@1.3.5': {} + '@types/mjml-core@4.15.1': + optional: true + + '@types/mjml@4.7.4': + dependencies: + '@types/mjml-core': 4.15.1 + optional: true + '@types/ms@0.7.34': {} '@types/multer@1.4.11': @@ -8706,6 +9480,8 @@ snapshots: dependencies: '@types/express': 4.17.21 + '@types/node@14.18.63': {} + '@types/node@20.14.2': dependencies: undici-types: 5.26.5 @@ -8758,6 +9534,9 @@ snapshots: '@types/prop-types@15.7.12': {} + '@types/pug@2.0.10': + optional: true + '@types/qs@6.9.15': {} '@types/range-parser@1.2.7': {} @@ -9034,6 +9813,9 @@ snapshots: acorn-walk@8.3.2: {} + acorn@7.4.1: + optional: true + acorn@8.11.3: {} acorn@8.14.0: {} @@ -9076,6 +9858,12 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + alce@1.2.0: + dependencies: + esprima: 1.2.5 + estraverse: 1.9.3 + optional: true + ansi-colors@4.1.3: {} ansi-escapes@4.3.2: @@ -9205,6 +9993,9 @@ snapshots: asap@2.0.6: {} + assert-never@1.4.0: + optional: true + ast-types-flow@0.0.8: {} astring@1.8.6: {} @@ -9303,6 +10094,11 @@ snapshots: babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.7) + babel-walk@3.0.0-canary-5: + dependencies: + '@babel/types': 7.24.7 + optional: true + bail@2.0.2: {} balanced-match@1.0.2: {} @@ -9355,6 +10151,9 @@ snapshots: transitivePeerDependencies: - supports-color + boolbase@1.0.0: + optional: true + bowser@2.11.0: {} brace-expansion@1.1.11: @@ -9436,6 +10235,12 @@ snapshots: callsites@3.1.0: {} + camel-case@3.0.0: + dependencies: + no-case: 2.3.2 + upper-case: 1.1.3 + optional: true + camelcase-css@2.0.1: {} camelcase@5.3.1: {} @@ -9454,6 +10259,12 @@ snapshots: escape-string-regexp: 1.0.5 supports-color: 5.5.0 + chalk@3.0.0: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + optional: true + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -9473,12 +10284,38 @@ snapshots: character-entities@2.0.2: {} + character-parser@2.2.0: + dependencies: + is-regex: 1.1.4 + optional: true + character-reference-invalid@1.1.4: {} character-reference-invalid@2.0.1: {} chardet@0.7.0: {} + cheerio-select@2.1.0: + 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.2.1 + optional: true + + cheerio@1.0.0-rc.12: + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.2.1 + htmlparser2: 8.0.2 + parse5: 7.2.1 + parse5-htmlparser2-tree-adapter: 7.1.0 + optional: true + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -9511,6 +10348,11 @@ snapshots: dependencies: clsx: 2.0.0 + clean-css@4.2.4: + dependencies: + source-map: 0.6.1 + optional: true + cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 @@ -9585,10 +10427,16 @@ snapshots: comma-separated-tokens@2.0.3: {} + commander@10.0.1: + optional: true + commander@2.20.3: {} commander@4.1.1: {} + commander@6.2.1: + optional: true + comment-json@4.2.5: dependencies: array-timsort: 1.0.3 @@ -9615,8 +10463,20 @@ snapshots: readable-stream: 3.6.2 typedarray: 0.0.6 + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + optional: true + consola@2.15.3: {} + constantinople@4.0.1: + dependencies: + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + optional: true + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -9681,6 +10541,15 @@ snapshots: create-require@1.1.1: {} + cross-spawn@6.0.6: + dependencies: + nice-try: 1.0.5 + path-key: 2.0.1 + semver: 5.7.2 + shebang-command: 1.2.0 + which: 1.3.1 + optional: true + cross-spawn@7.0.3: dependencies: path-key: 3.1.1 @@ -9693,6 +10562,18 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-select@5.1.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.2.1 + nth-check: 2.1.1 + optional: true + + css-what@6.1.0: + optional: true + cssesc@3.0.0: {} csstype@3.1.3: {} @@ -9739,6 +10620,9 @@ snapshots: dedent@1.5.3: {} + deep-extend@0.6.0: + optional: true + deep-is@0.1.4: {} deepmerge@4.3.1: {} @@ -9767,12 +10651,18 @@ snapshots: destroy@1.2.0: {} + detect-indent@6.1.0: + optional: true + detect-libc@2.0.3: {} detect-newline@3.1.0: {} detect-node-es@1.1.0: {} + detect-node@2.1.0: + optional: true + devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -9794,6 +10684,12 @@ snapshots: dependencies: path-type: 4.0.0 + display-notification@2.0.0: + dependencies: + escape-string-applescript: 1.0.0 + run-applescript: 3.2.0 + optional: true + dlv@1.1.3: {} doctrine@2.1.0: @@ -9804,6 +10700,55 @@ snapshots: dependencies: esutils: 2.0.3 + doctypes@1.1.0: + optional: true + + dom-serializer@1.4.1: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + optional: true + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + optional: true + + domelementtype@2.3.0: + optional: true + + domhandler@3.3.0: + dependencies: + domelementtype: 2.3.0 + optional: true + + domhandler@4.3.1: + dependencies: + domelementtype: 2.3.0 + optional: true + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + optional: true + + domutils@2.8.0: + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + optional: true + + domutils@3.2.1: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + optional: true + dotenv-expand@10.0.0: {} dotenv@16.4.5: {} @@ -9820,6 +10765,14 @@ snapshots: dependencies: safe-buffer: 5.2.1 + editorconfig@1.0.4: + dependencies: + '@one-ini/wasm': 0.1.1 + commander: 10.0.1 + minimatch: 9.0.1 + semver: 7.6.3 + optional: true + ee-first@1.1.1: {} ejs@3.1.10: @@ -9854,6 +10807,9 @@ snapshots: encodeurl@2.0.0: {} + encoding-japanese@2.2.0: + optional: true + enhanced-resolve@5.17.0: dependencies: graceful-fs: 4.2.11 @@ -9864,6 +10820,12 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.1 + entities@2.2.0: + optional: true + + entities@4.5.0: + optional: true + err-code@2.0.3: {} error-ex@1.3.2: @@ -9944,7 +10906,7 @@ snapshots: iterator.prototype: 1.1.2 safe-array-concat: 1.1.2 - es-module-lexer@1.6.0: {} + es-module-lexer@1.5.4: {} es-object-atoms@1.0.0: dependencies: @@ -9970,8 +10932,14 @@ snapshots: escalade@3.2.0: {} + escape-goat@3.0.0: + optional: true + escape-html@1.0.3: {} + escape-string-applescript@1.0.0: + optional: true + escape-string-regexp@1.0.5: {} escape-string-regexp@2.0.0: {} @@ -10242,6 +11210,9 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.11.3) eslint-visitor-keys: 3.4.3 + esprima@1.2.5: + optional: true + esprima@4.0.1: {} esquery@1.5.0: @@ -10252,6 +11223,9 @@ snapshots: dependencies: estraverse: 5.3.0 + estraverse@1.9.3: + optional: true + estraverse@4.3.0: {} estraverse@5.3.0: {} @@ -10290,6 +11264,17 @@ snapshots: events@3.3.0: {} + execa@0.10.0: + dependencies: + cross-spawn: 6.0.6 + get-stream: 3.0.0 + is-stream: 1.1.0 + npm-run-path: 2.0.2 + p-finally: 1.0.0 + signal-exit: 3.0.7 + strip-eof: 1.0.0 + optional: true + execa@5.1.1: dependencies: cross-spawn: 7.0.3 @@ -10384,6 +11369,9 @@ snapshots: transitivePeerDependencies: - supports-color + extend-object@1.0.0: + optional: true + extend-shallow@2.0.1: dependencies: is-extendable: 0.1.1 @@ -10482,6 +11470,16 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + fixpack@4.0.0: + dependencies: + alce: 1.2.0 + chalk: 3.0.0 + detect-indent: 6.1.0 + detect-newline: 3.1.0 + extend-object: 1.0.0 + rc: 1.2.8 + optional: true + flat-cache@3.2.0: dependencies: flatted: 3.3.1 @@ -10603,6 +11601,12 @@ snapshots: get-package-type@0.1.0: {} + get-port@5.1.1: + optional: true + + get-stream@3.0.0: + optional: true + get-stream@6.0.1: {} get-symbol-description@1.0.2: @@ -10625,6 +11629,14 @@ snapshots: glob-to-regexp@0.4.1: {} + glob@10.3.12: + dependencies: + foreground-child: 3.3.0 + jackspeak: 2.3.6 + minimatch: 9.0.5 + minipass: 7.1.2 + path-scurry: 1.11.1 + glob@10.4.1: dependencies: foreground-child: 3.1.1 @@ -10701,12 +11713,22 @@ snapshots: section-matter: 1.0.0 strip-bom-string: 1.0.0 - has-bigints@1.0.2: {} - - has-flag@3.0.0: {} - - has-flag@4.0.0: {} - + handlebars@4.7.8: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + optional: true + + has-bigints@1.0.2: {} + + has-flag@3.0.0: {} + + has-flag@4.0.0: {} + has-own-prop@2.0.0: {} has-property-descriptors@1.0.2: @@ -10772,6 +11794,9 @@ snapshots: dependencies: '@types/hast': 3.0.4 + he@1.2.0: + optional: true + hexoid@1.0.0: {} hosted-git-info@7.0.2: @@ -10780,8 +11805,52 @@ snapshots: html-escaper@2.0.2: {} + html-minifier@4.0.0: + dependencies: + camel-case: 3.0.0 + clean-css: 4.2.4 + commander: 2.20.3 + he: 1.2.0 + param-case: 2.1.1 + relateurl: 0.2.7 + uglify-js: 3.19.3 + optional: true + + html-to-text@9.0.5: + dependencies: + '@selderee/plugin-htmlparser2': 0.11.0 + deepmerge: 4.3.1 + dom-serializer: 2.0.0 + htmlparser2: 8.0.2 + selderee: 0.11.0 + optional: true + html-url-attributes@3.0.0: {} + htmlparser2@5.0.1: + dependencies: + domelementtype: 2.3.0 + domhandler: 3.3.0 + domutils: 2.8.0 + entities: 2.2.0 + optional: true + + htmlparser2@8.0.2: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.1 + entities: 4.5.0 + optional: true + + htmlparser2@9.1.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.1 + entities: 4.5.0 + optional: true + http-errors@2.0.0: dependencies: depd: 2.0.0 @@ -10798,6 +11867,11 @@ snapshots: dependencies: safer-buffer: 2.1.2 + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + optional: true + ieee754@1.2.1: {} ignore@5.3.1: {} @@ -10825,6 +11899,9 @@ snapshots: inherits@2.0.4: {} + ini@1.3.8: + optional: true + ini@4.1.3: {} inline-style-parser@0.1.1: {} @@ -10942,8 +12019,17 @@ snapshots: is-decimal@2.0.1: {} + is-docker@2.2.1: + optional: true + is-empty@1.2.0: {} + is-expression@4.0.0: + dependencies: + acorn: 7.4.1 + object-assign: 4.1.1 + optional: true + is-extendable@0.1.1: {} is-extglob@2.1.1: {} @@ -10984,6 +12070,9 @@ snapshots: is-plain-obj@4.1.0: {} + is-promise@2.2.2: + optional: true + is-reference@3.0.2: dependencies: '@types/estree': 1.0.5 @@ -10999,6 +12088,9 @@ snapshots: dependencies: call-bind: 1.0.7 + is-stream@1.1.0: + optional: true + is-stream@2.0.1: {} is-string@1.0.7: @@ -11026,6 +12118,11 @@ snapshots: call-bind: 1.0.7 get-intrinsic: 1.2.4 + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + optional: true + isarray@1.0.0: {} isarray@2.0.5: {} @@ -11085,6 +12182,12 @@ snapshots: reflect.getprototypeof: 1.0.6 set-function-name: 2.0.2 + jackspeak@2.3.6: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + jackspeak@3.4.0: dependencies: '@isaacs/cliui': 8.0.2 @@ -11514,8 +12617,23 @@ snapshots: jiti@1.21.3: {} + js-beautify@1.15.1: + dependencies: + config-chain: 1.1.13 + editorconfig: 1.0.4 + glob: 10.4.5 + js-cookie: 3.0.5 + nopt: 7.2.1 + optional: true + js-confetti@0.12.0: {} + js-cookie@3.0.5: + optional: true + + js-stringify@1.0.2: + optional: true + js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -11572,6 +12690,12 @@ snapshots: ms: 2.1.3 semver: 7.6.3 + jstransformer@1.0.0: + dependencies: + is-promise: 2.2.2 + promise: 7.3.1 + optional: true + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.8 @@ -11586,6 +12710,17 @@ snapshots: readable-stream: 2.3.8 setimmediate: 1.0.5 + juice@10.0.1: + dependencies: + cheerio: 1.0.0-rc.12 + commander: 6.2.1 + mensch: 0.3.4 + slick: 1.12.2 + web-resource-inliner: 6.0.1 + transitivePeerDependencies: + - encoding + optional: true + jwa@1.4.1: dependencies: buffer-equal-constant-time: 1.0.1 @@ -11615,6 +12750,9 @@ snapshots: dependencies: language-subtag-registry: 0.3.23 + leac@0.6.0: + optional: true + leven@3.1.0: {} levn@0.4.1: @@ -11622,8 +12760,22 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + libbase64@1.3.0: + optional: true + + libmime@5.3.6: + dependencies: + encoding-japanese: 2.2.0 + iconv-lite: 0.6.3 + libbase64: 1.3.0 + libqp: 2.1.1 + optional: true + libphonenumber-js@1.11.3: {} + libqp@2.1.1: + optional: true + lie@3.3.0: dependencies: immediate: 3.0.6 @@ -11636,6 +12788,16 @@ snapshots: lines-and-columns@2.0.4: {} + linkify-it@5.0.0: + dependencies: + uc.micro: 2.1.0 + optional: true + + liquidjs@10.20.0: + dependencies: + commander: 10.0.1 + optional: true + load-plugin@6.0.3: dependencies: '@npmcli/config': 8.3.4 @@ -11684,6 +12846,9 @@ snapshots: dependencies: js-tokens: 4.0.0 + lower-case@1.1.4: + optional: true + lru-cache@10.2.2: {} lru-cache@5.1.1: @@ -11694,6 +12859,27 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.0 + mailparser@3.7.2: + dependencies: + encoding-japanese: 2.2.0 + he: 1.2.0 + html-to-text: 9.0.5 + iconv-lite: 0.6.3 + libmime: 5.3.6 + linkify-it: 5.0.0 + mailsplit: 5.4.2 + nodemailer: 6.9.16 + punycode.js: 2.3.1 + tlds: 1.255.0 + optional: true + + mailsplit@5.4.2: + dependencies: + libbase64: 1.3.0 + libmime: 5.3.6 + libqp: 2.1.1 + optional: true + make-dir@4.0.0: dependencies: semver: 7.6.3 @@ -11828,6 +13014,9 @@ snapshots: memory-pager@1.5.0: optional: true + mensch@0.3.4: + optional: true + merge-descriptors@1.0.1: {} merge-descriptors@1.0.3: {} @@ -12081,6 +13270,11 @@ snapshots: dependencies: brace-expansion: 2.0.1 + minimatch@9.0.1: + dependencies: + brace-expansion: 2.0.1 + optional: true + minimatch@9.0.4: dependencies: brace-expansion: 2.0.1 @@ -12093,6 +13287,335 @@ snapshots: minipass@7.1.2: {} + mjml-accordion@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-body@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-button@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-carousel@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-cli@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + chokidar: 3.6.0 + glob: 10.4.5 + html-minifier: 4.0.0 + js-beautify: 1.15.1 + lodash: 4.17.21 + minimatch: 9.0.5 + 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 + transitivePeerDependencies: + - encoding + optional: true + + mjml-column@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-core@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + cheerio: 1.0.0-rc.12 + detect-node: 2.1.0 + html-minifier: 4.0.0 + js-beautify: 1.15.1 + juice: 10.0.1 + lodash: 4.17.21 + mjml-migrate: 4.15.3 + mjml-parser-xml: 4.15.3 + mjml-validator: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-divider@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-group@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-head-attributes@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-head-breakpoint@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-head-font@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-head-html-attributes@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-head-preview@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-head-style@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-head-title@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-head@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-hero@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-image@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-migrate@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + js-beautify: 1.15.1 + lodash: 4.17.21 + mjml-core: 4.15.3 + mjml-parser-xml: 4.15.3 + yargs: 17.7.2 + transitivePeerDependencies: + - encoding + optional: true + + mjml-navbar@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-parser-xml@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + detect-node: 2.1.0 + htmlparser2: 9.1.0 + lodash: 4.17.21 + optional: true + + mjml-preset-core@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + 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 + transitivePeerDependencies: + - encoding + optional: true + + mjml-raw@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-section@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-social@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-spacer@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-table@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-text@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml-validator@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + optional: true + + mjml-wrapper@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + lodash: 4.17.21 + mjml-core: 4.15.3 + mjml-section: 4.15.3 + transitivePeerDependencies: + - encoding + optional: true + + mjml@4.15.3: + dependencies: + '@babel/runtime': 7.24.7 + 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 + transitivePeerDependencies: + - encoding + optional: true + mkdirp@0.5.6: dependencies: minimist: 1.2.8 @@ -12223,6 +13746,14 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + nice-try@1.0.5: + optional: true + + no-case@2.3.2: + dependencies: + lower-case: 1.1.4 + optional: true + node-abort-controller@3.1.1: {} node-emoji@1.11.0: @@ -12239,6 +13770,8 @@ snapshots: node-releases@2.0.19: {} + nodemailer@6.9.16: {} + nopt@7.2.1: dependencies: abbrev: 2.0.0 @@ -12273,6 +13806,11 @@ snapshots: npm-package-arg: 11.0.3 semver: 7.6.3 + npm-run-path@2.0.2: + dependencies: + path-key: 2.0.1 + optional: true + npm-run-path@4.0.1: dependencies: path-key: 3.1.1 @@ -12281,6 +13819,11 @@ snapshots: nprogress@0.2.0: {} + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + optional: true + oauth@0.10.0: {} object-assign@4.1.1: {} @@ -12343,6 +13886,12 @@ snapshots: dependencies: mimic-fn: 2.1.0 + open@7.4.2: + dependencies: + is-docker: 2.2.1 + is-wsl: 2.2.0 + optional: true + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -12366,6 +13915,14 @@ snapshots: os-tmpdir@1.0.2: {} + p-event@4.2.0: + dependencies: + p-timeout: 3.2.0 + optional: true + + p-finally@1.0.0: + optional: true + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -12382,12 +13939,27 @@ snapshots: dependencies: p-limit: 3.1.0 + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + optional: true + p-try@2.2.0: {} + p-wait-for@3.2.0: + dependencies: + p-timeout: 3.2.0 + optional: true + package-json-from-dist@1.0.1: {} pako@1.0.11: {} + param-case@2.1.1: + dependencies: + no-case: 2.3.2 + optional: true + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -12414,19 +13986,36 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 parse-json@7.1.1: dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 error-ex: 1.3.2 json-parse-even-better-errors: 3.0.2 lines-and-columns: 2.0.4 type-fest: 3.13.1 + parse5-htmlparser2-tree-adapter@7.1.0: + dependencies: + domhandler: 5.0.3 + parse5: 7.2.1 + optional: true + + parse5@7.2.1: + dependencies: + entities: 4.5.0 + optional: true + + parseley@0.12.1: + dependencies: + leac: 0.6.0 + peberminta: 0.9.0 + optional: true + parseurl@1.3.3: {} passport-github@1.1.0: @@ -12446,6 +14035,11 @@ snapshots: dependencies: passport-strategy: 1.0.0 + passport-magic-login@1.2.2: + dependencies: + '@types/node': 14.18.63 + jsonwebtoken: 9.0.2 + passport-oauth2@1.8.0: dependencies: base64url: 3.0.1 @@ -12466,6 +14060,9 @@ snapshots: path-is-absolute@1.0.1: {} + path-key@2.0.1: + optional: true + path-key@3.1.1: {} path-parse@1.0.7: {} @@ -12487,6 +14084,9 @@ snapshots: pause@0.0.1: {} + peberminta@0.9.0: + optional: true + periscopic@3.1.0: dependencies: '@types/estree': 1.0.5 @@ -12571,6 +14171,21 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 + preview-email@3.1.0: + dependencies: + ci-info: 3.9.0 + display-notification: 2.0.0 + fixpack: 4.0.0 + get-port: 5.1.1 + mailparser: 3.7.2 + nodemailer: 6.9.16 + open: 7.4.2 + p-event: 4.2.0 + p-wait-for: 3.2.0 + pug: 3.0.3 + uuid: 9.0.1 + optional: true + proc-log@4.2.0: {} process-nextick-args@2.0.1: {} @@ -12582,6 +14197,11 @@ snapshots: err-code: 2.0.3 retry: 0.12.0 + promise@7.3.1: + dependencies: + asap: 2.0.6 + optional: true + prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -12595,6 +14215,9 @@ snapshots: property-information@6.5.0: {} + proto-list@1.2.4: + optional: true + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -12602,6 +14225,88 @@ snapshots: proxy-from-env@1.1.0: {} + pug-attrs@3.0.0: + dependencies: + constantinople: 4.0.1 + js-stringify: 1.0.2 + pug-runtime: 3.0.1 + optional: true + + pug-code-gen@3.0.3: + 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.2 + optional: true + + pug-error@2.1.0: + optional: true + + pug-filters@4.0.0: + dependencies: + constantinople: 4.0.1 + jstransformer: 1.0.0 + pug-error: 2.1.0 + pug-walk: 2.0.0 + resolve: 1.22.8 + optional: true + + pug-lexer@5.0.1: + dependencies: + character-parser: 2.2.0 + is-expression: 4.0.0 + pug-error: 2.1.0 + optional: true + + pug-linker@4.0.0: + dependencies: + pug-error: 2.1.0 + pug-walk: 2.0.0 + optional: true + + pug-load@3.0.0: + dependencies: + object-assign: 4.1.1 + pug-walk: 2.0.0 + optional: true + + pug-parser@6.0.0: + dependencies: + pug-error: 2.1.0 + token-stream: 1.0.0 + optional: true + + pug-runtime@3.0.1: + optional: true + + pug-strip-comments@2.0.0: + dependencies: + pug-error: 2.1.0 + optional: true + + pug-walk@2.0.0: + optional: true + + pug@3.0.3: + 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 + optional: true + + punycode.js@2.3.1: + optional: true + punycode@2.3.1: {} pure-rand@6.1.0: {} @@ -12633,6 +14338,14 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + optional: true + react-dom@18.2.0(react@18.2.0): dependencies: loose-envify: 1.4.0 @@ -12770,6 +14483,9 @@ snapshots: es-errors: 1.3.0 set-function-name: 2.0.2 + relateurl@0.2.7: + optional: true + remark-mdx@3.0.1: dependencies: mdast-util-mdx: 3.0.0 @@ -12843,6 +14559,11 @@ snapshots: dependencies: glob: 7.2.3 + run-applescript@3.2.0: + dependencies: + execa: 0.10.0 + optional: true + run-async@2.4.1: {} run-async@3.0.0: {} @@ -12904,6 +14625,14 @@ snapshots: extend-shallow: 2.0.1 kind-of: 6.0.3 + selderee@0.11.0: + dependencies: + parseley: 0.12.1 + optional: true + + semver@5.7.2: + optional: true + semver@6.3.1: {} semver@7.6.2: {} @@ -13014,10 +14743,18 @@ snapshots: '@img/sharp-win32-ia32': 0.33.4 '@img/sharp-win32-x64': 0.33.4 + shebang-command@1.2.0: + dependencies: + shebang-regex: 1.0.0 + optional: true + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 + shebang-regex@1.0.0: + optional: true + shebang-regex@3.0.0: {} side-channel-list@1.0.0: @@ -13069,6 +14806,9 @@ snapshots: slash@3.0.0: {} + slick@1.12.2: + optional: true + smart-buffer@4.2.0: {} socks@2.8.3: @@ -13209,8 +14949,14 @@ snapshots: strip-bom@4.0.0: {} + strip-eof@1.0.0: + optional: true + strip-final-newline@2.0.0: {} + strip-json-comments@2.0.1: + optional: true + strip-json-comments@3.1.1: {} strnum@1.0.5: {} @@ -13362,6 +15108,9 @@ snapshots: through@2.3.8: {} + tlds@1.255.0: + optional: true + tmp@0.0.33: dependencies: os-tmpdir: 1.0.2 @@ -13376,6 +15125,9 @@ snapshots: toidentifier@1.0.1: {} + token-stream@1.0.0: + optional: true + tr46@0.0.3: {} tr46@3.0.0: @@ -13583,6 +15335,12 @@ snapshots: typescript@5.7.2: {} + uc.micro@2.1.0: + optional: true + + uglify-js@3.19.3: + optional: true + uid2@0.0.4: {} uid@2.0.2: @@ -13695,6 +15453,9 @@ snapshots: escalade: 3.2.0 picocolors: 1.1.1 + upper-case@1.1.3: + optional: true + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -13735,6 +15496,9 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 + valid-data-url@3.0.1: + optional: true + validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 @@ -13778,6 +15542,9 @@ snapshots: unist-util-stringify-position: 4.0.0 vfile-message: 4.0.2 + void-elements@3.1.0: + optional: true + walk-up-path@3.0.1: {} walker@1.0.8: @@ -13793,6 +15560,18 @@ snapshots: dependencies: defaults: 1.0.4 + web-resource-inliner@6.0.1: + dependencies: + ansi-colors: 4.1.3 + escape-goat: 3.0.0 + htmlparser2: 5.0.1 + mime: 2.6.0 + node-fetch: 2.7.0 + valid-data-url: 3.0.1 + transitivePeerDependencies: + - encoding + optional: true + webidl-conversions@3.0.1: {} webidl-conversions@7.0.0: {} @@ -13812,7 +15591,7 @@ snapshots: browserslist: 4.24.3 chrome-trace-event: 1.0.4 enhanced-resolve: 5.18.0 - es-module-lexer: 1.6.0 + es-module-lexer: 1.5.4 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -13879,6 +15658,11 @@ snapshots: gopd: 1.0.1 has-tostringtag: 1.0.2 + which@1.3.1: + dependencies: + isexe: 2.0.0 + optional: true + which@2.0.2: dependencies: isexe: 2.0.0 @@ -13887,8 +15671,19 @@ snapshots: dependencies: isexe: 3.1.1 + with@7.0.2: + dependencies: + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + assert-never: 1.4.0 + babel-walk: 3.0.0-canary-5 + optional: true + word-wrap@1.2.5: {} + wordwrap@1.0.0: + optional: true + wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0 diff --git a/server/nest-cli.json b/server/nest-cli.json index 9d1eea5c..6893c53e 100644 --- a/server/nest-cli.json +++ b/server/nest-cli.json @@ -1,13 +1,21 @@ { - "$schema": "https://json.schemastore.org/nest-cli", - "collection": "@nestjs/schematics", - "sourceRoot": "src", - "entryFile": "server/src/main.js", - "compilerOptions": { - "deleteOutDir": true, - "assets": [ - "**/assets/**/*" - ], - "watchAssets": true - } + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "entryFile": "server/src/main.js", + "compilerOptions": { + "deleteOutDir": true, + "plugins": [ + "@nestjs/swagger/plugin" + ], + "assets": [ + { + "include": "mailing/templates/**/*.{hbs,png}", + "outDir": "dist/server/src/", + "exclude": "dist/**/*" + }, + "**/assets/**/*" + ], + "watchAssets": true + } } \ No newline at end of file diff --git a/server/package.json b/server/package.json index 0f24d493..34ee73e6 100644 --- a/server/package.json +++ b/server/package.json @@ -23,6 +23,7 @@ "@aws-sdk/client-s3": "^3.717.0", "@aws-sdk/s3-request-presigner": "^3.717.0", "@encode42/nbs.js": "^5.0.2", + "@nestjs-modules/mailer": "^2.0.2", "@nestjs/common": "^10.4.15", "@nestjs/config": "^3.3.0", "@nestjs/core": "^10.4.15", @@ -31,6 +32,7 @@ "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.4.15", "@nestjs/swagger": "^7.4.2", + "@nestjs/throttler": "^6.3.0", "@types/uuid": "^9.0.8", "axios": "^1.7.9", "bcryptjs": "^2.4.3", @@ -46,6 +48,7 @@ "passport-google-oauth20": "^2.0.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", + "passport-magic-login": "^1.2.2", "passport-oauth2": "^1.8.0", "reflect-metadata": "^0.1.14", "rxjs": "^7.8.1", @@ -54,6 +57,7 @@ "zod-validation-error": "^3.4.0" }, "devDependencies": { + "@faker-js/faker": "^9.3.0", "@nestjs/cli": "^10.4.9", "@nestjs/schematics": "^10.2.3", "@nestjs/testing": "^10.4.15", diff --git a/server/src/app.module.ts b/server/src/app.module.ts index ab916a76..97643ac5 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -1,14 +1,21 @@ import { Logger, Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { MongooseModule, MongooseModuleFactoryOptions } from '@nestjs/mongoose'; +import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler'; +import { MailerModule } from '@nestjs-modules/mailer'; +import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter'; import { AuthModule } from './auth/auth.module'; import { validate } from './config/EnvironmentVariables'; +import { EmailLoginModule } from './email-login/email-login.module'; import { FileModule } from './file/file.module'; +import { MailingModule } from './mailing/mailing.module'; import { ParseTokenPipe } from './parseToken'; +import { SeedModule } from './seed/seed.module'; import { SongModule } from './song/song.module'; import { SongBrowserModule } from './song-browser/song-browser.module'; import { UserModule } from './user/user.module'; +import { APP_GUARD } from '@nestjs/core'; @Module({ imports: [ @@ -34,14 +41,56 @@ import { UserModule } from './user/user.module'; }; }, }), + // Mailing + MailerModule.forRootAsync({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => { + const transport = configService.getOrThrow('MAIL_TRANSPORT'); + const from = configService.getOrThrow('MAIL_FROM'); + AppModule.logger.debug(`MAIL_TRANSPORT: ${transport}`); + AppModule.logger.debug(`MAIL_FROM: ${from}`); + return { + transport: transport, + defaults: { + from: from, + }, + template: { + dir: __dirname + '/mailing/templates', + adapter: new HandlebarsAdapter(), + options: { + strict: true, + }, + }, + }; + }, + inject: [ConfigService], + }), + // Throttler + ThrottlerModule.forRoot([ + { + ttl: 60, + limit: 256, // 256 requests per minute + }, + ]), SongModule, UserModule, AuthModule.forRootAsync(), FileModule.forRootAsync(), SongBrowserModule, + SeedModule.forRoot(), + EmailLoginModule, + MailingModule, ], controllers: [], - providers: [ParseTokenPipe], + providers: [ + ParseTokenPipe, + { + provide: APP_GUARD, + useClass: ThrottlerGuard, + }, + ], exports: [ParseTokenPipe], }) -export class AppModule {} +export class AppModule { + static readonly logger = new Logger(AppModule.name); +} diff --git a/server/src/auth/auth.controller.spec.ts b/server/src/auth/auth.controller.spec.ts index 52732362..501b2f93 100644 --- a/server/src/auth/auth.controller.spec.ts +++ b/server/src/auth/auth.controller.spec.ts @@ -3,12 +3,18 @@ import { Request, Response } from 'express'; import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; +import { MagicLinkEmailStrategy } from './strategies/magicLinkEmail.strategy'; const mockAuthService = { githubLogin: jest.fn(), googleLogin: jest.fn(), discordLogin: jest.fn(), verifyToken: jest.fn(), + loginWithEmail: jest.fn(), +}; + +const mockMagicLinkEmailStrategy = { + send: jest.fn(), }; describe('AuthController', () => { @@ -23,6 +29,10 @@ describe('AuthController', () => { provide: AuthService, useValue: mockAuthService, }, + { + provide: MagicLinkEmailStrategy, + useValue: mockMagicLinkEmailStrategy, + }, ], }).compile(); @@ -56,6 +66,13 @@ describe('AuthController', () => { }); }); + describe('githubLogin', () => { + it('should call AuthService.githubLogin', async () => { + await controller.githubLogin(); + expect(authService.githubLogin).toHaveBeenCalled(); + }); + }); + describe('googleRedirect', () => { it('should call AuthService.googleLogin', async () => { const req = {} as Request; @@ -78,6 +95,13 @@ describe('AuthController', () => { }); }); + describe('googleLogin', () => { + it('should call AuthService.googleLogin', async () => { + await controller.googleLogin(); + expect(authService.googleLogin).toHaveBeenCalled(); + }); + }); + describe('discordRedirect', () => { it('should call AuthService.discordLogin', async () => { const req = {} as Request; @@ -100,6 +124,46 @@ describe('AuthController', () => { }); }); + describe('discordLogin', () => { + it('should call AuthService.discordLogin', async () => { + await controller.discordLogin(); + expect(authService.discordLogin).toHaveBeenCalled(); + }); + }); + + describe('magicLinkRedirect', () => { + it('should call AuthService.loginWithEmail', async () => { + const req = {} as Request; + const res = {} as Response; + + await controller.magicLinkRedirect(req, res); + + expect(authService.loginWithEmail).toHaveBeenCalledWith(req, res); + }); + + it('should handle exceptions', async () => { + const req = {} as Request; + const res = {} as Response; + const error = new Error('Test error'); + (authService.loginWithEmail as jest.Mock).mockRejectedValueOnce(error); + + await expect(controller.magicLinkRedirect(req, res)).rejects.toThrow( + 'Test error', + ); + }); + }); + + describe('magicLinkLogin', () => { + it('should call AuthService.discordLogin', async () => { + const req = {} as Request; + const res = {} as Response; + + await controller.magicLinkLogin(req, res); + + expect(mockMagicLinkEmailStrategy.send).toHaveBeenCalledWith(req, res); + }); + }); + describe('verify', () => { it('should call AuthService.verifyToken', async () => { const req = {} as Request; diff --git a/server/src/auth/auth.controller.ts b/server/src/auth/auth.controller.ts index c51828e7..7481a8aa 100644 --- a/server/src/auth/auth.controller.ts +++ b/server/src/auth/auth.controller.ts @@ -1,50 +1,114 @@ -import { Controller, Get, Logger, Req, Res, UseGuards } from '@nestjs/common'; +import { + Controller, + Get, + Inject, + Logger, + Post, + Req, + Res, + UseGuards, +} from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; import type { Request, Response } from 'express'; +import { Throttle } from '@nestjs/throttler'; import { AuthService } from './auth.service'; +import { MagicLinkEmailStrategy } from './strategies/magicLinkEmail.strategy'; @Controller('auth') @ApiTags('auth') export class AuthController { private readonly logger = new Logger(AuthController.name); - constructor(private readonly authService: AuthService) {} + constructor( + @Inject(AuthService) + private readonly authService: AuthService, + @Inject(MagicLinkEmailStrategy) + private readonly magicLinkEmailStrategy: MagicLinkEmailStrategy, + ) {} + + @Throttle({ + default: { + // one every 1 hour + ttl: 60 * 60 * 1000, + limit: 1, + }, + }) + @Post('login/magic-link') + @ApiOperation({ + summary: + 'Will send the user a email with a single use login link, if the user does not exist it will create a new user', + requestBody: { + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + destination: { + type: 'string', + example: 'vycasnicolas@gmail.com', + description: 'Email address to send the magic link to', + }, + }, + required: ['destination'], + }, + }, + }, + }, + }) + public async magicLinkLogin(@Req() req: Request, @Res() res: Response) { + return this.magicLinkEmailStrategy.send(req, res); + } + + @Get('magic-link/callback') + @ApiOperation({ + summary: 'Will send the user a email with a single use login link', + }) + @UseGuards(AuthGuard('magic-link')) + public async magicLinkRedirect(@Req() req: Request, @Res() res: Response) { + return this.authService.loginWithEmail(req, res); + } @Get('login/github') @UseGuards(AuthGuard('github')) + @ApiOperation({ summary: 'Login with github' }) public githubLogin() { // Not need for implementation, its handled by passport - this.logger.log('GitHub login'); + this.logger.debug('GitHub login'); } @Get('github/callback') @UseGuards(AuthGuard('github')) + @ApiOperation({ summary: 'GitHub login callback' }) public githubRedirect(@Req() req: Request, @Res() res: Response) { return this.authService.githubLogin(req, res); } @Get('login/google') + @ApiOperation({ summary: 'Login with google' }) @UseGuards(AuthGuard('google')) public googleLogin() { // Not need for implementation, its handled by passport - this.logger.log('Google login'); + this.logger.debug('Google login'); } @Get('google/callback') + @ApiOperation({ summary: 'Google login callback' }) @UseGuards(AuthGuard('google')) public googleRedirect(@Req() req: Request, @Res() res: Response) { return this.authService.googleLogin(req, res); } @Get('login/discord') + @ApiOperation({ summary: 'Login with discord' }) @UseGuards(AuthGuard('discord')) public discordLogin() { // Not need for implementation, its handled by passport - this.logger.log('Discord login'); + this.logger.debug('Discord login'); } @Get('discord/callback') + @ApiOperation({ summary: 'Discord login callback' }) @UseGuards(AuthGuard('discord')) public discordRedirect(@Req() req: Request, @Res() res: Response) { return this.authService.discordLogin(req, res); @@ -62,7 +126,6 @@ export class AuthController { }) res: Response, ) { - // Not need for implementation, its handled by passport this.authService.verifyToken(req, res); } } diff --git a/server/src/auth/auth.module.ts b/server/src/auth/auth.module.ts index 6bf98698..8a3f1765 100644 --- a/server/src/auth/auth.module.ts +++ b/server/src/auth/auth.module.ts @@ -2,6 +2,7 @@ import { DynamicModule, Logger, Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { JwtModule } from '@nestjs/jwt'; +import { MailingModule } from '@server/mailing/mailing.module'; import { UserModule } from '@server/user/user.module'; import { AuthController } from './auth.controller'; @@ -10,6 +11,7 @@ import { DiscordStrategy } from './strategies/discord.strategy'; import { GithubStrategy } from './strategies/github.strategy'; import { GoogleStrategy } from './strategies/google.strategy'; import { JwtStrategy } from './strategies/JWT.strategy'; +import { MagicLinkEmailStrategy } from './strategies/magicLinkEmail.strategy'; @Module({}) export class AuthModule { @@ -19,6 +21,7 @@ export class AuthModule { imports: [ UserModule, ConfigModule.forRoot(), + MailingModule, JwtModule.registerAsync({ inject: [ConfigService], imports: [ConfigModule], @@ -49,6 +52,7 @@ export class AuthModule { GoogleStrategy, GithubStrategy, DiscordStrategy, + MagicLinkEmailStrategy, JwtStrategy, { inject: [ConfigService], diff --git a/server/src/auth/auth.service.spec.ts b/server/src/auth/auth.service.spec.ts index 4fa1e388..77dbe99e 100644 --- a/server/src/auth/auth.service.spec.ts +++ b/server/src/auth/auth.service.spec.ts @@ -45,6 +45,10 @@ describe('AuthService', () => { provide: JwtService, useValue: mockJwtService, }, + { + provide: 'COOKIE_EXPIRES_IN', + useValue: '3600', + }, { provide: 'FRONTEND_URL', useValue: 'http://frontend.test.com', diff --git a/server/src/auth/auth.service.ts b/server/src/auth/auth.service.ts index 5d83b8d2..a28ca3eb 100644 --- a/server/src/auth/auth.service.ts +++ b/server/src/auth/auth.service.ts @@ -21,11 +21,11 @@ export class AuthService { private readonly userService: UserService, @Inject(JwtService) private readonly jwtService: JwtService, + @Inject('COOKIE_EXPIRES_IN') + private readonly COOKIE_EXPIRES_IN: string, @Inject('FRONTEND_URL') private readonly FRONTEND_URL: string, - @Inject('COOKIE_EXPIRES_IN') - private readonly COOKIE_EXPIRES_IN: string, @Inject('JWT_SECRET') private readonly JWT_SECRET: string, @Inject('JWT_EXPIRES_IN') @@ -190,7 +190,17 @@ export class AuthService { return this.GenTokenRedirect(user_registered, res); } - private async createJwtPayload(payload: TokenPayload): Promise { + public async loginWithEmail(req: Request, res: Response) { + const user = req.user as UserDocument; + + if (!user) { + return res.redirect(this.FRONTEND_URL + '/login'); + } + + return this.GenTokenRedirect(user, res); + } + + public async createJwtPayload(payload: TokenPayload): Promise { const [accessToken, refreshToken] = await Promise.all([ this.jwtService.signAsync(payload, { secret: this.JWT_SECRET, diff --git a/server/src/auth/strategies/magicLinkEmail.strategy.spec.ts b/server/src/auth/strategies/magicLinkEmail.strategy.spec.ts new file mode 100644 index 00000000..50c4165b --- /dev/null +++ b/server/src/auth/strategies/magicLinkEmail.strategy.spec.ts @@ -0,0 +1,152 @@ +import { ConfigService } from '@nestjs/config'; +import { Test, TestingModule } from '@nestjs/testing'; + +import { MailingService } from '@server/mailing/mailing.service'; +import { UserService } from '@server/user/user.service'; + +import { MagicLinkEmailStrategy } from './magicLinkEmail.strategy'; + +describe('MagicLinkEmailStrategy', () => { + let strategy: MagicLinkEmailStrategy; + let userService: UserService; + let mailingService: MailingService; + let configService: ConfigService; + + const mockUserService = { + findByEmail: jest.fn(), + createWithEmail: jest.fn(), + }; + + const mockMailingService = { + sendEmail: jest.fn(), + }; + + const mockConfigService = { + get: jest.fn((key: string) => { + if (key === 'MAGIC_LINK_SECRET') return 'test_secret'; + if (key === 'SERVER_URL') return 'http://localhost:3000'; + return null; + }), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + MagicLinkEmailStrategy, + { provide: UserService, useValue: mockUserService }, + { provide: MailingService, useValue: mockMailingService }, + { provide: ConfigService, useValue: mockConfigService }, + { + provide: 'MAGIC_LINK_SECRET', + useValue: 'test_secret', + }, + { + provide: 'SERVER_URL', + useValue: 'http://localhost:3000', + }, + ], + }).compile(); + + strategy = module.get(MagicLinkEmailStrategy); + userService = module.get(UserService); + mailingService = module.get(MailingService); + configService = module.get(ConfigService); + }); + + it('should be defined', () => { + expect(strategy).toBeDefined(); + }); + + describe('sendMagicLink', () => { + it('should send a magic link email', async () => { + const email = 'test@example.com'; + + const magicLink = + 'http://localhost/api/v1/auth/magic-link/callback?token=test_token'; + + const user = { username: 'testuser', email }; + + mockUserService.findByEmail.mockResolvedValue(user); + + await MagicLinkEmailStrategy.sendMagicLink( + 'http://localhost:3000', + userService, + mailingService, + )(email, magicLink); + + expect(mockUserService.findByEmail).toHaveBeenCalledWith(email); + + expect(mockMailingService.sendEmail).toHaveBeenCalledWith({ + to: email, + context: { + magicLink: + 'http://localhost/api/v1/auth/magic-link/callback?token=test_token', + username: 'testuser', + }, + subject: 'Noteblock Magic Link', + template: 'magic-link', + }); + }); + + it('should create a new user if not found and send a magic link email', async () => { + const email = 'testuser@test.com'; + + const magicLink = + 'http://localhost/api/v1/auth/magic-link/callback?token=test_token'; + + const user = { username: 'testuser', email }; + + mockUserService.findByEmail.mockResolvedValue(null); + mockUserService.createWithEmail.mockResolvedValue(user); + + await MagicLinkEmailStrategy.sendMagicLink( + 'http://localhost:3000', + userService, + mailingService, + )(email, magicLink); + + expect(mockUserService.findByEmail).toHaveBeenCalledWith(email); + + expect(mockMailingService.sendEmail).toHaveBeenCalledWith({ + to: email, + context: { + magicLink: + 'http://localhost/api/v1/auth/magic-link/callback?token=test_token', + username: 'testuser', + }, + subject: 'Welcome to Noteblock.world', + template: 'magic-link-new-account', + }); + }); + }); + + describe('validate', () => { + it('should validate the payload and return the user', async () => { + const payload = { destination: 'test@example.com' }; + const user = { username: 'testuser', email: 'test@example.com' }; + + mockUserService.findByEmail.mockResolvedValue(user); + + const result = await strategy.validate(payload); + + expect(result).toEqual(user); + }); + + it('should create a new user if not found and return the user', async () => { + const payload = { destination: 'test@example.com' }; + + mockUserService.findByEmail.mockResolvedValue(null); + mockUserService.createWithEmail.mockResolvedValue({ + email: 'test@example.com', + username: 'test', + }); + + const result = await strategy.validate(payload); + + expect(result).toEqual({ + email: 'test@example.com', + username: 'test', + }); + }); + }); +}); diff --git a/server/src/auth/strategies/magicLinkEmail.strategy.ts b/server/src/auth/strategies/magicLinkEmail.strategy.ts new file mode 100644 index 00000000..eb528158 --- /dev/null +++ b/server/src/auth/strategies/magicLinkEmail.strategy.ts @@ -0,0 +1,96 @@ +import { Inject, Injectable, Logger } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import strategy from 'passport-magic-login'; + +import { MailingService } from '@server/mailing/mailing.service'; +import { UserService } from '@server/user/user.service'; + +type authenticationLinkPayload = { + destination: string; +}; + +type magicLinkCallback = (error: any, user: any) => void; + +@Injectable() +export class MagicLinkEmailStrategy extends PassportStrategy( + strategy, + 'magic-link', +) { + static logger = new Logger(MagicLinkEmailStrategy.name); + + constructor( + @Inject('MAGIC_LINK_SECRET') + MAGIC_LINK_SECRET: string, + @Inject('SERVER_URL') + SERVER_URL: string, + @Inject(UserService) + private readonly userService: UserService, + @Inject(MailingService) + private readonly mailingService: MailingService, + ) { + super({ + secret: MAGIC_LINK_SECRET, + confirmUrl: `${SERVER_URL}/api/v1/auth/magic-link/confirm`, + callbackUrl: `${SERVER_URL}/api/v1/auth/magic-link/callback`, + sendMagicLink: MagicLinkEmailStrategy.sendMagicLink( + SERVER_URL, + userService, + mailingService, + ), + verify: ( + payload: authenticationLinkPayload, + callback: magicLinkCallback, + ) => { + callback(null, this.validate(payload)); + }, + }); + } + + static sendMagicLink = + ( + SERVER_URL: string, + userService: UserService, + mailingService: MailingService, + ) => + async (email: string, magicLink: string) => { + const user = await userService.findByEmail(email); + + if (!user) { + mailingService.sendEmail({ + to: email, + context: { + magicLink: magicLink, + username: email.split('@')[0], + }, + subject: 'Welcome to Noteblock.world', + template: 'magic-link-new-account', + }); + } else { + mailingService.sendEmail({ + to: email, + context: { + magicLink: magicLink, + username: user.username, + }, + subject: 'Noteblock Magic Link', + template: 'magic-link', + }); + } + }; + + async validate(payload: authenticationLinkPayload) { + MagicLinkEmailStrategy.logger.debug( + `Validating payload: ${JSON.stringify(payload)}`, + ); + + const user = await this.userService.findByEmail(payload.destination); + + if (!user) { + return await this.userService.createWithEmail(payload.destination); + } + + MagicLinkEmailStrategy.logger.debug(`User found: ${user.username}`); + + return user; + } +} diff --git a/server/src/config/EnvironmentVariables.ts b/server/src/config/EnvironmentVariables.ts index 0ff380e0..cbb15109 100644 --- a/server/src/config/EnvironmentVariables.ts +++ b/server/src/config/EnvironmentVariables.ts @@ -11,6 +11,7 @@ export class EnvironmentVariables { @IsOptional() NODE_ENV?: Environment; + // OAuth providers @IsString() GITHUB_CLIENT_ID: string; @@ -29,6 +30,11 @@ export class EnvironmentVariables { @IsString() DISCORD_CLIENT_SECRET: string; + // Email magic link auth + @IsString() + MAGIC_LINK_SECRET: string; + + // jwt auth @IsString() JWT_SECRET: string; @@ -41,6 +47,7 @@ export class EnvironmentVariables { @IsString() JWT_REFRESH_EXPIRES_IN: string; + // database @IsString() MONGO_URL: string; @@ -52,11 +59,12 @@ export class EnvironmentVariables { @IsString() @IsOptional() - APP_DOMAIN?: string; + APP_DOMAIN: string = 'localhost'; @IsString() RECAPTCHA_KEY: string; + // s3 @IsString() S3_ENDPOINT: string; @@ -79,6 +87,7 @@ export class EnvironmentVariables { @IsOptional() WHITELISTED_USERS?: string; + // discord webhook @IsString() DISCORD_WEBHOOK_URL: string; diff --git a/server/src/email-login/email-login.controller.spec.ts b/server/src/email-login/email-login.controller.spec.ts new file mode 100644 index 00000000..1e8e48a3 --- /dev/null +++ b/server/src/email-login/email-login.controller.spec.ts @@ -0,0 +1,21 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { EmailLoginController } from './email-login.controller'; +import { EmailLoginService } from './email-login.service'; + +describe('EmailLoginController', () => { + let controller: EmailLoginController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [EmailLoginController], + providers: [EmailLoginService], + }).compile(); + + controller = module.get(EmailLoginController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server/src/email-login/email-login.controller.ts b/server/src/email-login/email-login.controller.ts new file mode 100644 index 00000000..8b6be0a6 --- /dev/null +++ b/server/src/email-login/email-login.controller.ts @@ -0,0 +1,8 @@ +import { Controller } from '@nestjs/common'; + +import { EmailLoginService } from './email-login.service'; + +@Controller('email-login') +export class EmailLoginController { + constructor(private readonly emailLoginService: EmailLoginService) {} +} diff --git a/server/src/email-login/email-login.module.ts b/server/src/email-login/email-login.module.ts new file mode 100644 index 00000000..47414fa8 --- /dev/null +++ b/server/src/email-login/email-login.module.ts @@ -0,0 +1,10 @@ +import { Module } from '@nestjs/common'; + +import { EmailLoginController } from './email-login.controller'; +import { EmailLoginService } from './email-login.service'; + +@Module({ + controllers: [EmailLoginController], + providers: [EmailLoginService], +}) +export class EmailLoginModule {} diff --git a/server/src/email-login/email-login.service.spec.ts b/server/src/email-login/email-login.service.spec.ts new file mode 100644 index 00000000..1424b132 --- /dev/null +++ b/server/src/email-login/email-login.service.spec.ts @@ -0,0 +1,19 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { EmailLoginService } from './email-login.service'; + +describe('EmailLoginService', () => { + let service: EmailLoginService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [EmailLoginService], + }).compile(); + + service = module.get(EmailLoginService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server/src/email-login/email-login.service.ts b/server/src/email-login/email-login.service.ts new file mode 100644 index 00000000..177317d5 --- /dev/null +++ b/server/src/email-login/email-login.service.ts @@ -0,0 +1,4 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class EmailLoginService {} diff --git a/server/src/file/file.module.ts b/server/src/file/file.module.ts index 22946c14..33303b61 100644 --- a/server/src/file/file.module.ts +++ b/server/src/file/file.module.ts @@ -12,39 +12,40 @@ export class FileModule { providers: [ { provide: 'S3_BUCKET_SONGS', - inject: [ConfigService], useFactory: (configService: ConfigService) => configService.getOrThrow('S3_BUCKET_SONGS'), + inject: [ConfigService], }, { provide: 'S3_BUCKET_THUMBS', - inject: [ConfigService], useFactory: (configService: ConfigService) => configService.getOrThrow('S3_BUCKET_THUMBS'), + inject: [ConfigService], }, { provide: 'S3_KEY', - inject: [ConfigService], useFactory: (configService: ConfigService) => configService.getOrThrow('S3_KEY'), + inject: [ConfigService], }, { provide: 'S3_SECRET', - inject: [ConfigService], useFactory: (configService: ConfigService) => configService.getOrThrow('S3_SECRET'), + + inject: [ConfigService], }, { provide: 'S3_ENDPOINT', - inject: [ConfigService], useFactory: (configService: ConfigService) => configService.getOrThrow('S3_ENDPOINT'), + inject: [ConfigService], }, { provide: 'S3_REGION', - inject: [ConfigService], useFactory: (configService: ConfigService) => configService.getOrThrow('S3_REGION'), + inject: [ConfigService], }, FileService, ], diff --git a/server/src/file/file.service.spec.ts b/server/src/file/file.service.spec.ts index 569be7aa..f07b23ed 100644 --- a/server/src/file/file.service.spec.ts +++ b/server/src/file/file.service.spec.ts @@ -1,5 +1,6 @@ import { GetObjectCommand, + HeadBucketCommand, PutObjectCommand, S3Client, } from '@aws-sdk/client-s3'; @@ -17,6 +18,7 @@ jest.mock('@aws-sdk/client-s3', () => { S3Client: jest.fn(() => mS3Client), GetObjectCommand: jest.fn(), PutObjectCommand: jest.fn(), + HeadBucketCommand: jest.fn(), ObjectCannedACL: { private: 'private', public_read: 'public-read', @@ -72,17 +74,23 @@ describe('FileService', () => { expect(fileService).toBeDefined(); }); - it('should throw an error if S3 configuration is missing', () => { - expect(() => { - new FileService( - '', - 'test-bucket-thumbs', - 'test-key', - 'test-secret', - 'test-endpoint', - 'test-region', - ); - }).toThrow('Missing S3 bucket configuration'); + describe('verifyBucket', () => { + it('should verify the buckets successfully', async () => { + (s3Client.send as jest.Mock).mockResolvedValueOnce({}); + (s3Client.send as jest.Mock).mockResolvedValueOnce({}); + + await fileService['verifyBucket'](); + + expect(s3Client.send).toHaveBeenCalledWith(expect.any(HeadBucketCommand)); + expect(s3Client.send).toHaveBeenCalledWith(expect.any(HeadBucketCommand)); + }); + + it('should log an error if bucket verification fails', async () => { + const error = new Error('Bucket not found'); + (s3Client.send as jest.Mock).mockRejectedValueOnce(error); + + await expect(fileService['verifyBucket']()).rejects.toThrow(error); + }); }); it('should upload a song', async () => { diff --git a/server/src/file/file.service.ts b/server/src/file/file.service.ts index 9b0061d9..95bb6052 100644 --- a/server/src/file/file.service.ts +++ b/server/src/file/file.service.ts @@ -2,6 +2,7 @@ import * as path from 'path'; import { GetObjectCommand, + HeadBucketCommand, ObjectCannedACL, PutObjectCommand, S3Client, @@ -14,8 +15,6 @@ export class FileService { private readonly logger = new Logger(FileService.name); private s3Client: S3Client; private region: string; - private bucketSongs: string; - private bucketThumbs: string; constructor( @Inject('S3_BUCKET_SONGS') @@ -32,16 +31,36 @@ export class FileService { @Inject('S3_REGION') private readonly S3_REGION: string, ) { - const bucketSongs = S3_BUCKET_SONGS; - const bucketThumbs = S3_BUCKET_THUMBS; this.s3Client = this.getS3Client(); + // verify that the bucket exists - if (!(bucketSongs && bucketThumbs)) { - throw new Error('Missing S3 bucket configuration'); - } + this.verifyBucket(); + } - this.bucketSongs = bucketSongs; - this.bucketThumbs = bucketThumbs; + private async verifyBucket() { + try { + this.logger.debug( + `Verifying buckets ${this.S3_BUCKET_SONGS} and ${this.S3_BUCKET_THUMBS}`, + ); + + await Promise.all([ + this.s3Client.send( + new HeadBucketCommand({ Bucket: this.S3_BUCKET_SONGS }), + ), + this.s3Client.send( + new HeadBucketCommand({ Bucket: this.S3_BUCKET_THUMBS }), + ), + ]); + + this.logger.debug('Buckets verification successful'); + } catch (error) { + this.logger.error( + `Error verifying buckets ${this.S3_BUCKET_SONGS} and ${this.S3_BUCKET_THUMBS}`, + error, + ); + + throw error; + } } private getS3Client() { @@ -61,6 +80,7 @@ export class FileService { accessKeyId: key, secretAccessKey: secret, }, + forcePathStyle: endpoint.includes('localhost') ? true : false, }); return s3Config; @@ -68,7 +88,7 @@ export class FileService { // Uploads a song to the S3 bucket and returns the key public async uploadSong(buffer: Buffer, publicId: string) { - const bucket = this.bucketSongs; + const bucket = this.S3_BUCKET_SONGS; const fileName = 'songs/' + path.parse(publicId).name.replace(/\s/g, '') + '.nbs'; @@ -87,7 +107,7 @@ export class FileService { } public async uploadPackedSong(buffer: Buffer, publicId: string) { - const bucket = this.bucketSongs; + const bucket = this.S3_BUCKET_SONGS; const fileName = 'packed/' + path.parse(publicId).name.replace(/\s/g, '') + '.zip'; @@ -106,7 +126,7 @@ export class FileService { } public async getSongDownloadUrl(key: string, filename: string) { - const bucket = this.bucketSongs; + const bucket = this.S3_BUCKET_SONGS; const command = new GetObjectCommand({ Bucket: bucket, @@ -122,7 +142,7 @@ export class FileService { } public async uploadThumbnail(buffer: Buffer, publicId: string) { - const bucket = this.bucketThumbs; + const bucket = this.S3_BUCKET_THUMBS; const fileName = 'thumbs/' + path.parse(publicId).name.replace(/\s/g, '') + '.png'; @@ -141,18 +161,22 @@ export class FileService { } public getThumbnailUrl(key: string) { - const bucket = this.bucketThumbs; + const bucket = this.S3_BUCKET_THUMBS; const url = this.getPublicFileUrl(key, bucket); return url; } private getPublicFileUrl(key: string, bucket: string) { const region = this.region; - return `https://${bucket}.s3.${region}.backblazeb2.com/${key}`; + if (this.S3_ENDPOINT.includes('localhost')) { + // minio url + return `${this.S3_ENDPOINT}/${bucket}/${key}`; + } // production blackblaze url + else return `https://${bucket}.s3.${region}.backblazeb2.com/${key}`; // TODO: make possible to use custom domain } public async deleteSong(nbsFileUrl: string) { - const bucket = this.bucketSongs; + const bucket = this.S3_BUCKET_SONGS; const command = new GetObjectCommand({ Bucket: bucket, @@ -200,7 +224,7 @@ export class FileService { } public async getSongFile(nbsFileUrl: string): Promise { - const bucket = this.bucketSongs; + const bucket = this.S3_BUCKET_SONGS; const command = new GetObjectCommand({ Bucket: bucket, diff --git a/server/src/mailing/mailing.controller.spec.ts b/server/src/mailing/mailing.controller.spec.ts new file mode 100644 index 00000000..cebfd371 --- /dev/null +++ b/server/src/mailing/mailing.controller.spec.ts @@ -0,0 +1,26 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { MailingController } from './mailing.controller'; +import { MailingService } from './mailing.service'; + +describe('MailingController', () => { + let controller: MailingController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [MailingController], + providers: [ + { + provide: MailingService, + useValue: {}, + }, + ], + }).compile(); + + controller = module.get(MailingController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server/src/mailing/mailing.controller.ts b/server/src/mailing/mailing.controller.ts new file mode 100644 index 00000000..51a4e0ff --- /dev/null +++ b/server/src/mailing/mailing.controller.ts @@ -0,0 +1,8 @@ +import { Controller } from '@nestjs/common'; + +import { MailingService } from './mailing.service'; + +@Controller() +export class MailingController { + constructor(private readonly mailingService: MailingService) {} +} diff --git a/server/src/mailing/mailing.module.ts b/server/src/mailing/mailing.module.ts new file mode 100644 index 00000000..fb737e0e --- /dev/null +++ b/server/src/mailing/mailing.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; + +import { MailingController } from './mailing.controller'; +import { MailingService } from './mailing.service'; + +@Module({ + controllers: [MailingController], + providers: [MailingService], + exports: [MailingService], +}) +export class MailingModule {} diff --git a/server/src/mailing/mailing.service.spec.ts b/server/src/mailing/mailing.service.spec.ts new file mode 100644 index 00000000..4918150e --- /dev/null +++ b/server/src/mailing/mailing.service.spec.ts @@ -0,0 +1,68 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { MailerService } from '@nestjs-modules/mailer'; + +import { MailingService } from './mailing.service'; + +const MockedMailerService = { + sendMail: jest.fn(), +}; + +describe('MailingService', () => { + let service: MailingService; + let mailerService: MailerService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + MailingService, + { + provide: MailerService, + useValue: MockedMailerService, + }, + ], + }).compile(); + + service = module.get(MailingService); + mailerService = module.get(MailerService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should send an email using a template', async () => { + const sendMailSpy = jest + .spyOn(mailerService, 'sendMail') + .mockResolvedValueOnce(undefined); + + const to = 'accountHolder@example.com'; + const subject = 'Test Email'; + const template = 'hello'; + + const context = { + name: 'John Doe', + message: 'Hello, this is a test email!', + }; + + await service.sendEmail({ to, subject, template, context }); + + expect(sendMailSpy).toHaveBeenCalledWith({ + to, + subject, + template, + context, + attachments: [ + { + filename: 'background-image.png', + cid: 'background-image', + path: `${__dirname}/templates/img/background-image.png`, + }, + { + filename: 'logo.png', + cid: 'logo', + path: `${__dirname}/templates/img/logo.png`, + }, + ], + }); + }); +}); diff --git a/server/src/mailing/mailing.service.ts b/server/src/mailing/mailing.service.ts new file mode 100644 index 00000000..8fd5b447 --- /dev/null +++ b/server/src/mailing/mailing.service.ts @@ -0,0 +1,53 @@ +import { Inject, Injectable, Logger } from '@nestjs/common'; +import { MailerService } from '@nestjs-modules/mailer'; + +interface EmailOptions { + to: string; + subject: string; + template: string; + context: { + [name: string]: any; + }; +} + +@Injectable() +export class MailingService { + private readonly logger = new Logger(MailingService.name); + constructor( + @Inject(MailerService) + private readonly mailerService: MailerService, + ) {} + + async sendEmail({ + to, + subject, + template, + context, + }: EmailOptions): Promise { + try { + await this.mailerService.sendMail({ + to, + subject, + template: `${template}`, // The template file name (without extension) + context, // The context to be passed to the template + attachments: [ + { + filename: 'background-image.png', + cid: 'background-image', + path: `${__dirname}/templates/img/background-image.png`, + }, + { + filename: 'logo.png', + cid: 'logo', + path: `${__dirname}/templates/img/logo.png`, + }, + ], + }); + + this.logger.debug(`Email sent to ${to}`); + } catch (error) { + this.logger.error(`Failed to send email to ${to}`, error); + throw error; + } + } +} diff --git a/server/src/mailing/templates/img/background-image.png b/server/src/mailing/templates/img/background-image.png new file mode 100644 index 00000000..b39ef844 Binary files /dev/null and b/server/src/mailing/templates/img/background-image.png differ diff --git a/server/src/mailing/templates/img/logo.png b/server/src/mailing/templates/img/logo.png new file mode 100644 index 00000000..2211d621 Binary files /dev/null and b/server/src/mailing/templates/img/logo.png differ diff --git a/server/src/mailing/templates/magic-link-new-account.hbs b/server/src/mailing/templates/magic-link-new-account.hbs new file mode 100644 index 00000000..cd951c1a --- /dev/null +++ b/server/src/mailing/templates/magic-link-new-account.hbs @@ -0,0 +1,65 @@ + + + + + + + Welcome to OpenNoteBlockWorld! + + + + +
+ +

Welcome, {{username}}!

+

Click the button below to log in to your account:

+ Login +

If you did not request this login link, please ignore this email.

+
+

OpenNoteBlockWorld

+
+
+ + + \ No newline at end of file diff --git a/server/src/mailing/templates/magic-link.hbs b/server/src/mailing/templates/magic-link.hbs new file mode 100644 index 00000000..d07c9b9b --- /dev/null +++ b/server/src/mailing/templates/magic-link.hbs @@ -0,0 +1,67 @@ + + + + + + + Login to Your Account + + + + +
+
+ +

Hello, {{username}}!

+

Click the button below to log in to your account:

+ Login +

If you did not request this login link, please ignore this email.

+
+

OpenNoteBlockWorld

+
+
+
+ + + \ No newline at end of file diff --git a/server/src/seed/seed.controller.spec.ts b/server/src/seed/seed.controller.spec.ts new file mode 100644 index 00000000..63cc91ea --- /dev/null +++ b/server/src/seed/seed.controller.spec.ts @@ -0,0 +1,26 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { SeedController } from './seed.controller'; +import { SeedService } from './seed.service'; + +describe('SeedController', () => { + let controller: SeedController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [SeedController], + providers: [ + { + provide: SeedService, + useValue: {}, + }, + ], + }).compile(); + + controller = module.get(SeedController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/server/src/seed/seed.controller.ts b/server/src/seed/seed.controller.ts new file mode 100644 index 00000000..ae82f980 --- /dev/null +++ b/server/src/seed/seed.controller.ts @@ -0,0 +1,21 @@ +import { Controller, Get } from '@nestjs/common'; +import { ApiOperation, ApiTags } from '@nestjs/swagger'; + +import { SeedService } from './seed.service'; + +@Controller('seed') +@ApiTags('seed') +export class SeedController { + constructor(private readonly seedService: SeedService) {} + + @Get('seed-dev') + @ApiOperation({ + summary: 'Seed the database with development data', + }) + async seed() { + this.seedService.seedDev(); + return { + message: 'Seeding in progress', + }; + } +} diff --git a/server/src/seed/seed.module.ts b/server/src/seed/seed.module.ts new file mode 100644 index 00000000..bf4ded79 --- /dev/null +++ b/server/src/seed/seed.module.ts @@ -0,0 +1,40 @@ +import { env } from 'node:process'; + +import { DynamicModule, Logger, Module } from '@nestjs/common'; +import { ConfigModule, ConfigService } from '@nestjs/config'; + +import { SongModule } from '@server/song/song.module'; +import { UserModule } from '@server/user/user.module'; + +import { SeedController } from './seed.controller'; +import { SeedService } from './seed.service'; + +@Module({}) +export class SeedModule { + private static readonly logger = new Logger(SeedModule.name); + static forRoot(): DynamicModule { + if (env.NODE_ENV !== 'development') { + SeedModule.logger.warn('Seeding is only allowed in development mode'); + return { + module: SeedModule, + }; + } else { + SeedModule.logger.warn('Seeding is allowed in development mode'); + return { + module: SeedModule, + imports: [UserModule, SongModule, ConfigModule.forRoot()], + providers: [ + ConfigService, + SeedService, + { + provide: 'NODE_ENV', + useFactory: (configService: ConfigService) => + configService.get('NODE_ENV'), + inject: [ConfigService], + }, + ], + controllers: [SeedController], + }; + } + } +} diff --git a/server/src/seed/seed.service.spec.ts b/server/src/seed/seed.service.spec.ts new file mode 100644 index 00000000..7577e9c4 --- /dev/null +++ b/server/src/seed/seed.service.spec.ts @@ -0,0 +1,41 @@ +import { Test, TestingModule } from '@nestjs/testing'; + +import { SongService } from '@server/song/song.service'; +import { UserService } from '@server/user/user.service'; + +import { SeedService } from './seed.service'; + +describe('SeedService', () => { + let service: SeedService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + SeedService, + { + provide: 'NODE_ENV', + useValue: 'development', + }, + { + provide: UserService, + useValue: { + createWithPassword: jest.fn(), + }, + }, + { + provide: SongService, + useValue: { + uploadSong: jest.fn(), + getSongById: jest.fn(), + }, + }, + ], + }).compile(); + + service = module.get(SeedService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/server/src/seed/seed.service.ts b/server/src/seed/seed.service.ts new file mode 100644 index 00000000..2d8c6771 --- /dev/null +++ b/server/src/seed/seed.service.ts @@ -0,0 +1,223 @@ +import { Instrument, Note, Song } from '@encode42/nbs.js'; +import { faker } from '@faker-js/faker'; +import { + HttpException, + HttpStatus, + Inject, + Injectable, + Logger, +} from '@nestjs/common'; +import { UploadConst } from '@shared/validation/song/constants'; +import { + CategoryType, + LicenseType, + VisibilityType, +} from '@shared/validation/song/dto/types'; +import { UploadSongDto } from '@shared/validation/song/dto/UploadSongDto.dto'; + +import { SongDocument } from '@server/song/entity/song.entity'; +import { SongService } from '@server/song/song.service'; +import { UserDocument } from '@server/user/entity/user.entity'; +import { UserService } from '@server/user/user.service'; + +@Injectable() +export class SeedService { + public readonly logger = new Logger(SeedService.name); + constructor( + @Inject('NODE_ENV') + private readonly NODE_ENV: string, + + @Inject(UserService) + private readonly userService: UserService, + + @Inject(SongService) + private readonly songService: SongService, + ) {} + + public async seedDev() { + if (this.NODE_ENV !== 'development') { + this.logger.error('Seeding is only allowed in development mode'); + throw new HttpException('Unauthorized', HttpStatus.UNAUTHORIZED); + } + + const createdUsers = await this.seedUsers(); + this.logger.log(`Created ${createdUsers.length} users`); + const createdSongs = await this.seedSongs(createdUsers); + this.logger.log(`Created ${createdSongs.length} songs`); + } + + private async seedUsers() { + const createdUsers: UserDocument[] = []; + + for (let i = 0; i < 100; i++) { + const user = await this.userService.createWithEmail( + faker.internet.email(), + ); + + //change user creation date + (user as any).createdAt = this.generateRandomDate( + new Date(2020, 0, 1), + new Date(), + ); + + user.loginCount = faker.helpers.rangeToNumber({ min: 0, max: 1000 }); + user.playCount = faker.helpers.rangeToNumber({ min: 0, max: 1000 }); + user.description = faker.lorem.paragraph(); + + user.socialLinks = { + youtube: faker.internet.url(), + x: faker.internet.url(), + discord: faker.internet.url(), + instagram: faker.internet.url(), + twitch: faker.internet.url(), + bandcamp: faker.internet.url(), + facebook: faker.internet.url(), + github: faker.internet.url(), + reddit: faker.internet.url(), + snapchat: faker.internet.url(), + soundcloud: faker.internet.url(), + spotify: faker.internet.url(), + steam: faker.internet.url(), + telegram: faker.internet.url(), + tiktok: faker.internet.url(), + }; + + // remove some social links randomly to simulate users not having all of them or having none + for (const key in Object.keys(user.socialLinks)) + if (faker.datatype.boolean()) delete (user.socialLinks as any)[key]; + + createdUsers.push(await this.userService.update(user)); + } + + return createdUsers; + } + + private async seedSongs(users: UserDocument[]) { + const songs: SongDocument[] = []; + const licenses = Object.keys(UploadConst.licenses); + const categories = Object.keys(UploadConst.categories); + const visibilities = Object.keys(UploadConst.visibility); + + for (const user of users) { + // most users will have 0-5 songs and a few will have 5-10, not a real statist by whatever I just what to test the system in development mode + const songCount = this.generateExponentialRandom(5, 2, 0.5, 10); + + for (let i = 0; i < songCount; i++) { + const nbsSong = this.generateRandomSong(); + const fileData = nbsSong.toArrayBuffer(); + const fileBuffer = Buffer.from(fileData); + + const body: UploadSongDto = { + file: { + buffer: fileData, + size: fileBuffer.length, + mimetype: 'application/octet-stream', + originalname: `${faker.music.songName()}.nbs`, + }, + allowDownload: faker.datatype.boolean(), + visibility: faker.helpers.arrayElement( + visibilities, + ) as VisibilityType, + title: faker.music.songName(), + originalAuthor: faker.music.artist(), + description: faker.lorem.paragraph(), + license: faker.helpers.arrayElement(licenses) as LicenseType, + category: faker.helpers.arrayElement(categories) as CategoryType, + customInstruments: [], + thumbnailData: { + backgroundColor: faker.internet.color(), + startLayer: faker.helpers.rangeToNumber({ min: 0, max: 4 }), + startTick: faker.helpers.rangeToNumber({ min: 0, max: 100 }), + zoomLevel: faker.helpers.rangeToNumber({ min: 1, max: 5 }), + }, + }; + + const uploadSongResponse = await this.songService.uploadSong({ + user, + body, + file: body.file, + }); + + const song = await this.songService.getSongById( + uploadSongResponse.publicId, + ); + + if (!song) continue; + + //change song creation date + (song as any).createdAt = this.generateRandomDate( + new Date(2020, 0, 1), + new Date(), + ); + + song.playCount = faker.helpers.rangeToNumber({ min: 0, max: 1000 }); + song.downloadCount = faker.helpers.rangeToNumber({ min: 0, max: 1000 }); + song.likeCount = faker.helpers.rangeToNumber({ min: 0, max: 1000 }); + await song.save(); + + songs.push(song); + } + } + + return songs; + } + + private generateExponentialRandom( + start = 1, + stepScale = 2, + stepProbability = 0.5, + limit = Number.MAX_SAFE_INTEGER, + ) { + let max = start; + + while (faker.datatype.boolean(stepProbability) && max < limit) { + max *= stepScale; + } + + return faker.number.int({ min: 0, max: Math.min(max, limit) }); + } + + private generateRandomSong() { + const songTest = new Song(); + songTest.meta.author = faker.music.artist(); + songTest.meta.description = faker.lorem.sentence(); + songTest.meta.name = faker.music.songName(); + songTest.meta.originalAuthor = faker.music.artist(); + + songTest.tempo = faker.helpers.rangeToNumber({ min: 20 * 1, max: 20 * 4 }); + const layerCount = faker.helpers.rangeToNumber({ min: 1, max: 5 }); + + for (let layerIndex = 0; layerIndex < layerCount; layerIndex++) { + const instrument = Instrument.builtIn[layerCount]; + const layer = songTest.createLayer(); + layer.meta.name = instrument.meta.name; + + const notes = Array.from({ + length: faker.helpers.rangeToNumber({ min: 20, max: 120 }), + }).map( + () => + new Note(instrument, { + key: faker.helpers.rangeToNumber({ min: 0, max: 127 }), + velocity: faker.helpers.rangeToNumber({ min: 0, max: 127 }), + panning: faker.helpers.rangeToNumber({ min: -1, max: 1 }), + pitch: faker.helpers.rangeToNumber({ min: -1, max: 1 }), + }), + ); + + for (let i = 0; i < notes.length; i++) + songTest.setNote(i * 4, layer, notes[i]); + // "i * 4" is placeholder - this is the tick to place on + } + + return songTest; + } + + private generateRandomDate(from: Date, to: Date): Date { + return new Date( + faker.date.between({ + from: from.getTime(), + to: to.getTime(), + }), + ); + } +} diff --git a/server/src/song/my-songs/my-songs.controller.ts b/server/src/song/my-songs/my-songs.controller.ts index 666d85e3..236b91e1 100644 --- a/server/src/song/my-songs/my-songs.controller.ts +++ b/server/src/song/my-songs/my-songs.controller.ts @@ -1,6 +1,6 @@ import { Controller, Get, Query, UseGuards } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; -import { ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; +import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { PageQueryDTO } from '@shared/validation/common/dto/PageQuery.dto'; import { SongPageDto } from '@shared/validation/song/dto/SongPageDto'; @@ -11,6 +11,7 @@ import { SongService } from '../song.service'; @UseGuards(AuthGuard('jwt-refresh')) @Controller('my-songs') +@ApiTags('song') export class MySongsController { constructor(public readonly songService: SongService) {} diff --git a/server/src/song/song-upload/song-upload.service.ts b/server/src/song/song-upload/song-upload.service.ts index 99f8bbd9..5bbd92d1 100644 --- a/server/src/song/song-upload/song-upload.service.ts +++ b/server/src/song/song-upload/song-upload.service.ts @@ -275,19 +275,12 @@ export class SongUploadService { } private prepareSongForUpload( - buffer: Buffer, + songFileBuffer: Buffer, body: UploadSongDto, user: UserDocument, ): { nbsSong: Song; songBuffer: Buffer } { - const loadedArrayBuffer = new ArrayBuffer(buffer.byteLength); - const view = new Uint8Array(loadedArrayBuffer); - - for (let i = 0; i < buffer.byteLength; ++i) { - view[i] = buffer[i]; - } - // Is the uploaded file a valid .nbs file? - const nbsSong = this.getSongObject(loadedArrayBuffer); + const nbsSong = this.getSongObject(songFileBuffer); // Update NBS file with form values injectSongFileMetadata( @@ -444,6 +437,7 @@ export class SongUploadService { { error: { file: 'Invalid NBS file', + errors: nbsSong.errors, }, }, HttpStatus.BAD_REQUEST, diff --git a/server/src/song/song.service.ts b/server/src/song/song.service.ts index 4a2e5ea6..07345e65 100644 --- a/server/src/song/song.service.ts +++ b/server/src/song/song.service.ts @@ -40,6 +40,12 @@ export class SongService { private songWebhookService: SongWebhookService, ) {} + public async getSongById(publicId: string) { + return this.songModel.findOne({ + publicId, + }); + } + public async uploadSong({ file, user, diff --git a/server/src/user/entity/user.entity.ts b/server/src/user/entity/user.entity.ts index 8a16b236..87748e9b 100644 --- a/server/src/user/entity/user.entity.ts +++ b/server/src/user/entity/user.entity.ts @@ -1,6 +1,26 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { HydratedDocument, Schema as MongooseSchema } from 'mongoose'; +@Schema({}) +class SocialLinks { + bandcamp?: string; + discord?: string; + facebook?: string; + github?: string; + instagram?: string; + reddit?: string; + snapchat?: string; + soundcloud?: string; + spotify?: string; + steam?: string; + telegram?: string; + tiktok?: string; + threads?: string; + twitch?: string; + x?: string; + youtube?: string; +} + @Schema({ timestamps: true, toJSON: { @@ -42,31 +62,24 @@ export class User { @Prop({ type: String, required: true, unique: true }) email: string; - @Prop({ type: String, required: true, default: '#' }) + @Prop({ type: String, required: false, default: null }) + singleUsePass?: string; + + @Prop({ type: String, required: false, default: null }) + singleUsePassID?: string; + + @Prop({ type: String, required: true, default: 'No description provided' }) description: string; - @Prop({ type: String, required: true, default: '#' }) + @Prop({ + type: String, + required: true, + default: '/img/note-block-pfp.jpg', + }) profileImage: string; - @Prop({ type: Map, of: String, required: false, default: {} }) - socialLinks: { - bandcamp?: string; - discord?: string; - facebook?: string; - github?: string; - instagram?: string; - reddit?: string; - snapchat?: string; - soundcloud?: string; - spotify?: string; - steam?: string; - telegram?: string; - tiktok?: string; - threads?: string; - twitch?: string; - x?: string; - youtube?: string; - }; + @Prop({ type: SocialLinks, required: false, default: {} }) + socialLinks: SocialLinks; @Prop({ type: Boolean, required: true, default: true }) prefersDarkTheme: boolean; diff --git a/server/src/user/user.service.ts b/server/src/user/user.service.ts index 2565db5f..412da11b 100644 --- a/server/src/user/user.service.ts +++ b/server/src/user/user.service.ts @@ -24,6 +24,40 @@ export class UserService { return await user.save(); } + public async update(user: UserDocument): Promise { + try { + return (await this.userModel.findByIdAndUpdate(user._id, user, { + new: true, // return the updated document + })) as UserDocument; + } catch (error) { + throw new Error(error); + } + } + + public async createWithEmail(email: string): Promise { + // verify if user exists same email, username or publicName + const userByEmail = await this.findByEmail(email); + + if (userByEmail) { + throw new HttpException( + 'Email already registered', + HttpStatus.BAD_REQUEST, + ); + } + + const emailPrefixUsername = await this.generateUsername( + email.split('@')[0], + ); + + const user = await this.userModel.create({ + email: email, + username: emailPrefixUsername, + publicName: emailPrefixUsername, + }); + + return user; + } + public async findByEmail(email: string): Promise { const user = await this.userModel.findOne({ email }).exec(); @@ -36,6 +70,20 @@ export class UserService { return user; } + public async findByPublicName( + publicName: string, + ): Promise { + const user = await this.userModel.findOne({ publicName }); + + return user; + } + + public async findByUsername(username: string): Promise { + const user = await this.userModel.findOne({ username }); + + return user; + } + public async getUserPaginated(query: PageQueryDTO) { const { page = 1, limit = 10, sort = 'createdAt', order = 'asc' } = query; diff --git a/shared/validation/user/dto/Login.dto copy.ts b/shared/validation/user/dto/Login.dto copy.ts new file mode 100644 index 00000000..b433a0d2 --- /dev/null +++ b/shared/validation/user/dto/Login.dto copy.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class LoginDto { + @ApiProperty() + @IsString() + @IsNotEmpty() + public email: string; +} diff --git a/shared/validation/user/dto/LoginWithEmail.dto.ts b/shared/validation/user/dto/LoginWithEmail.dto.ts new file mode 100644 index 00000000..27c2d9cc --- /dev/null +++ b/shared/validation/user/dto/LoginWithEmail.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class LoginWithEmailDto { + @ApiProperty() + @IsString() + @IsNotEmpty() + public email: string; +} diff --git a/shared/validation/user/dto/NewEmailUser.dto.ts b/shared/validation/user/dto/NewEmailUser.dto.ts new file mode 100644 index 00000000..33be8301 --- /dev/null +++ b/shared/validation/user/dto/NewEmailUser.dto.ts @@ -0,0 +1,30 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + IsEmail, + IsNotEmpty, + IsString, + MaxLength, + MinLength, +} from 'class-validator'; + +export class NewEmailUserDto { + @ApiProperty({ + description: 'User name', + example: 'Tomast1337', + }) + @IsString() + @IsNotEmpty() + @MaxLength(64) + @MinLength(4) + username: string; + + @ApiProperty({ + description: 'User email', + example: 'vycasnicolas@gmail.com', + }) + @IsString() + @IsNotEmpty() + @MaxLength(64) + @IsEmail() + email: string; +} diff --git a/shared/validation/user/dto/SingleUsePass.dto.ts b/shared/validation/user/dto/SingleUsePass.dto.ts new file mode 100644 index 00000000..e1e04c25 --- /dev/null +++ b/shared/validation/user/dto/SingleUsePass.dto.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsString } from 'class-validator'; + +export class SingleUsePassDto { + @ApiProperty() + @IsString() + @IsNotEmpty() + id: string; + + @ApiProperty() + @IsString() + @IsNotEmpty() + pass: string; +} diff --git a/web/public/img/note-block-pfp.jpg b/web/public/img/note-block-pfp.jpg new file mode 100644 index 00000000..8c33e3fc Binary files /dev/null and b/web/public/img/note-block-pfp.jpg differ diff --git a/web/src/app/(external)/(auth)/login/email/page.tsx b/web/src/app/(external)/(auth)/login/email/page.tsx new file mode 100644 index 00000000..aae7d856 --- /dev/null +++ b/web/src/app/(external)/(auth)/login/email/page.tsx @@ -0,0 +1,18 @@ +import { Metadata } from 'next'; +import { redirect } from 'next/navigation'; + +import { LoginWithEmailPage } from '@web/src/modules/auth/components/loginWithEmailPage'; +import { checkLogin } from '@web/src/modules/auth/features/auth.utils'; + +export const metadata: Metadata = { + title: 'Sign in', +}; + +const Login = async () => { + const isLogged = await checkLogin(); + if (isLogged) redirect('/'); + + return ; +}; + +export default Login; diff --git a/web/src/lib/axios/ClientAxios.ts b/web/src/lib/axios/ClientAxios.ts index 52f42192..bdb14b2c 100644 --- a/web/src/lib/axios/ClientAxios.ts +++ b/web/src/lib/axios/ClientAxios.ts @@ -12,11 +12,15 @@ const ClientAxios = axios.create({ // Add a request interceptor to add the token to the request ClientAxios.interceptors.request.use( (config) => { - const token = getTokenLocal(); + try { + const token = getTokenLocal(); - config.headers.authorization = `Bearer ${token}`; + config.headers.authorization = `Bearer ${token}`; - return config; + return config; + } catch { + return config; + } }, (error) => { return Promise.reject(error); diff --git a/web/src/modules/auth/components/client/LoginFrom.tsx b/web/src/modules/auth/components/client/LoginFrom.tsx new file mode 100644 index 00000000..dca3a3e4 --- /dev/null +++ b/web/src/modules/auth/components/client/LoginFrom.tsx @@ -0,0 +1,126 @@ +'use client'; +import { faExclamationCircle } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import Link from 'next/link'; +import { FC, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { toast } from 'react-hot-toast'; + +import ClientAxios from '@web/src/lib/axios/ClientAxios'; +import { + Input, + SubmitButton, +} from '../../../shared/components/client/FormElements'; + +type LoginFormData = { + email: string; +}; + +const backendURL = process.env.NEXT_PUBLIC_API_URL; + +// TODO: Implement login logic +export const LoginForm: FC = () => { + const [isLoading, setIsLoading] = useState(false); + const [isLocked] = useState(false); + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm(); + + const onSubmit = async ({ email }: LoginFormData) => { + try { + setIsLoading(true); + const url = `${backendURL}/auth/login/magic-link`; + + const response = await ClientAxios.post(url, { + destination: email, + }); + + console.log(response.data); + + toast.success(`A magic link has been sent to ${email}!`, { + position: 'top-center', + duration: 20_000, // 20 seconds + }); + + toast.success('It will stay valid for one hour!', { + position: 'top-center', + duration: 20_000, // 20 seconds + }); + } catch (error) { + if ((error as any).isAxiosError) { + const axiosError = error as any; + + if (axiosError.response) { + const { status } = axiosError.response; + + if (status === 429) { + toast.error('Too many requests. Please try again later.', { + position: 'top-center', + duration: 20_000, // 20 seconds + }); + } else { + toast.error('An unexpected error occurred', { + position: 'top-center', + duration: 20_000, // 20 seconds + }); + } + } + } else { + toast.error('An unexpected error occurred', { + position: 'top-center', + duration: 20_000, // 20 seconds + }); + } + } finally { + setIsLoading(false); + } + }; + + return ( + <> +
+
+ +

+ Please make sure to carefully review our{' '} + + Community Guidelines + {' '} + before logging in! +

+
+ +
+ {/* Email */} +
+ +

Enter your email address to log in.

+ + } + isLoading={isLoading} + disabled={isLocked} + errorMessage={errors.email?.message} + {...register('email', { required: 'Email is required' })} + /> +
+ +
+ {/* Submit button */} + +
+
+
+ + ); +}; diff --git a/web/src/modules/auth/components/loginPage.tsx b/web/src/modules/auth/components/loginPage.tsx index 075d7f04..99251ab8 100644 --- a/web/src/modules/auth/components/loginPage.tsx +++ b/web/src/modules/auth/components/loginPage.tsx @@ -3,6 +3,7 @@ import { faGithub, faGoogle, } from '@fortawesome/free-brands-svg-icons'; +import { faRightToBracket } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import Link from 'next/link'; @@ -62,18 +63,19 @@ export const LoginPage = () => {

to discover, share and listen to note block music

- {/* -
- - We are running on a whitelist for the beta stage. Please wait for an - invitation to sign in! -
- */} -
+ {/* Login with Email */} + + + Log in with email + {/* Login with Google */} { + return ( +
+
+ {/* Left half */} + + + {/* Vertical divider (mobile) */} +
+ {/* Horizontal divider (desktop) */} +
+ + {/* Right half */} +
+ +
+
+ +
+ ); +}; diff --git a/web/src/modules/my-songs/components/client/context/MySongs.context.tsx b/web/src/modules/my-songs/components/client/context/MySongs.context.tsx index c6d043a9..c79ae133 100644 --- a/web/src/modules/my-songs/components/client/context/MySongs.context.tsx +++ b/web/src/modules/my-songs/components/client/context/MySongs.context.tsx @@ -13,7 +13,7 @@ import { useEffect, useState, } from 'react'; -import toast from 'react-hot-toast'; +import { toast } from 'react-hot-toast'; import axiosInstance from '@web/src/lib/axios'; import { getTokenLocal } from '@web/src/lib/axios/token.utils'; diff --git a/web/src/modules/shared/components/client/FormElements.tsx b/web/src/modules/shared/components/client/FormElements.tsx index 1f8eeb77..87e280b6 100644 --- a/web/src/modules/shared/components/client/FormElements.tsx +++ b/web/src/modules/shared/components/client/FormElements.tsx @@ -284,3 +284,34 @@ export const EditButton = ({ isDisabled }: { isDisabled: boolean }) => { ); }; + +export const SubmitButton = ({ isDisabled }: { isDisabled: boolean }) => { + return ( + + ); +}; + +export const Button = forwardRef< + HTMLButtonElement, + React.ButtonHTMLAttributes & { + isDisabled?: boolean; + } +>((props, ref) => { + const { isDisabled, ...rest } = props; + + return ( +