diff --git a/.dockerignore b/.dockerignore
index 28e2caa516ff..4a93b252fd1f 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -20,4 +20,4 @@ frontend/.env
#backend
backend/src/credentials/*.json
backend/.env
-backend/build
+backend/dist
diff --git a/.eslintignore b/.eslintignore
index 37c1ee3980a6..26f7c9ee66a5 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,3 +1,5 @@
-backend/build
+backend/dist
backend/__migration__
-docker
\ No newline at end of file
+docker
+backend/scripts
+**/vitest.config.js
diff --git a/.eslintrc.json b/.eslintrc.json
deleted file mode 100644
index 734ac1dcfcc0..000000000000
--- a/.eslintrc.json
+++ /dev/null
@@ -1,112 +0,0 @@
-{
- "env": {
- "browser": true,
- "es2021": true,
- "node": true
- },
- "globals": {
- "$": "readonly",
- "jQuery": "readonly",
- "html2canvas": "readonly",
- "ClipboardItem": "readonly",
- "grecaptcha": "readonly"
- },
- "ignorePatterns": [
- "backend/__tests__/**/*",
- "backend/jest.config.ts",
- "**/*.css",
- "**/*.scss",
- "frontend/static/js/*",
- "frontend/__tests__/**/*",
- "frontend/jest.config.ts"
- ],
- "extends": [
- "eslint:recommended",
- "plugin:json/recommended",
- "plugin:import/recommended",
- "plugin:import/typescript",
- "prettier"
- ],
- "plugins": ["json", "require-path-exists", "@typescript-eslint"],
- "rules": {
- "json/*": ["error"],
- "indent": ["off"],
- "no-empty": ["error", { "allowEmptyCatch": true }],
- "no-var": 2,
- "no-duplicate-imports": ["error"],
- // "import/default": "off",
- "import/no-unresolved": [
- "error",
- {
- "ignore": ["^./constants/firebase-config$"]
- }
- ],
- "import/no-duplicates": "off",
- "no-mixed-operators": [
- "error",
- {
- "groups": [["+", "??"]]
- }
- ]
- },
- "settings": {
- "import/resolver": {
- "typescript": {
- "alwaysTryTypes": true,
- "project": ["frontend/tsconfig.json", "backend/tsconfig.json"]
- },
- "node": true
- }
- },
- "overrides": [
- {
- // enable the rule specifically for TypeScript files
- "files": ["*.ts", "*.tsx"],
- "extends": [
- "plugin:@typescript-eslint/eslint-recommended",
- "plugin:@typescript-eslint/recommended",
- "plugin:@typescript-eslint/strict"
- // "plugin:@typescript-eslint/recommended-requiring-type-checking"
- ],
- "rules": {
- "@typescript-eslint/explicit-function-return-type": ["error"],
- "@typescript-eslint/ban-ts-comment": "off",
- "@typescript-eslint/no-empty-function": "warn",
- "@typescript-eslint/no-unused-vars": [
- "warn",
- { "argsIgnorePattern": "^(_|e|event)", "varsIgnorePattern": "^_" }
- ],
- "@typescript-eslint/no-var-requires": "error",
- "@typescript-eslint/no-this-alias": "off",
- "@typescript-eslint/no-misused-promises": [
- "error",
- {
- "checksVoidReturn": false
- }
- ],
- "@typescript-eslint/promise-function-async": "warn",
- "@typescript-eslint/no-floating-promises": "error",
- "@typescript-eslint/strict-boolean-expressions": [
- "error",
- { "allowNullableBoolean": true, "allowNullableNumber": true }
- ],
- //
- "@typescript-eslint/non-nullable-type-assertion-style": "off",
- "@typescript-eslint/no-unnecessary-condition": "off",
- "@typescript-eslint/consistent-type-definitions": ["warn", "type"],
- "@typescript-eslint/no-invalid-void-type": "off"
- },
- "parserOptions": {
- "ecmaVersion": 12,
- "sourceType": "module",
- "project": "**/tsconfig.json"
- }
- },
- {
- "files": ["backend/**/*.ts"],
- "rules": {
- "eqeqeq": "error"
- }
- }
- ]
-}
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000000..39330d4a6f58
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,7 @@
+version: 2
+updates:
+ - package-ecosystem: "npm"
+ directory: "/"
+ versioning-strategy: increase
+ schedule:
+ interval: "weekly"
\ No newline at end of file
diff --git a/.github/labeler.yml b/.github/labeler.yml
index aa81efd93f99..605b342b2c32 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -14,5 +14,8 @@ docs:
frontend:
- any: ["frontend/**/*"]
+packages:
+ - any: ["packages/**/*"]
+
local dev:
- any: ["**/gulpfile.js", "**/tsconfig.json"]
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index d7029e96bee9..82a1a6f71357 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -12,9 +12,9 @@
- Also please add a screenshot of the theme, it would be extra awesome if you do so!
- [ ] Check if any open issues are related to this PR; if so, be sure to tag them below.
- [ ] Make sure the PR title follows the Conventional Commits standard. (https://www.conventionalcommits.org for more info)
-- [ ] Make sure to include your GitHub username inside parentheses at the end of the PR title
+- [ ] Make sure to include your GitHub username prefixed with @ inside parentheses at the end of the PR title.
-
+
diff --git a/.github/workflows/monkey-ci.yml b/.github/workflows/monkey-ci.yml
index f4bcaf8670eb..0757a783d640 100644
--- a/.github/workflows/monkey-ci.yml
+++ b/.github/workflows/monkey-ci.yml
@@ -1,7 +1,8 @@
name: Monkey CI
env:
- NODE_VERSION: "18.19.1"
+ PNPM_VERSION: "9.6.0"
+ NODE_VERSION: "20.16.0"
RECAPTCHA_SITE_KEY: "6Lc-V8McAAAAAJ7s6LGNe7MBZnRiwbsbiWts87aj"
permissions:
@@ -22,17 +23,23 @@ concurrency:
jobs:
pre-ci:
- if: github.event.pull_request.draft == false
+ if: github.event.pull_request.draft == false || contains(github.event.pull_request.labels.*.name, 'force-ci') || contains(github.event.pull_request.labels.*.name, 'force-full-ci')
name: pre-ci
runs-on: ubuntu-latest
outputs:
should-build-be: ${{ steps.export-changes.outputs.should-build-be }}
should-build-fe: ${{ steps.export-changes.outputs.should-build-fe }}
+ should-build-pkg: ${{ steps.export-changes.outputs.should-build-pkg }}
assets-json: ${{ steps.export-changes.outputs.assets-json }}
steps:
- - uses: actions/checkout@v4
- - uses: dorny/paths-filter@v2
+ - name: Full checkout
+ uses: actions/checkout@v4
+ # paths filter doesn't need checkout on pr
+ if: github.event_name != 'pull_request'
+
+ - name: Detect changes
+ uses: dorny/paths-filter@v3
id: filter
with:
filters: |
@@ -40,8 +47,12 @@ jobs:
- 'frontend/**/*.json'
be-src:
- 'backend/**/*.{ts,js,json,lua,css,html}'
+ - 'backend/package.json'
fe-src:
- 'frontend/**/*.{ts,scss}'
+ - 'frontend/package.json'
+ pkg-src:
+ - 'packages/**/*'
anti-cheat:
- 'backend/**/anticheat/**'
@@ -52,46 +63,171 @@ jobs:
- name: Export changes
id: export-changes
run: |
+ echo "should-build-pkg=${{ steps.filter.outputs.pkg-src }}" >> $GITHUB_OUTPUT
echo "should-build-be=${{ steps.filter.outputs.be-src }}" >> $GITHUB_OUTPUT
echo "should-build-fe=${{ steps.filter.outputs.fe-src }}" >> $GITHUB_OUTPUT
echo "assets-json=${{ steps.filter.outputs.json }}" >> $GITHUB_OUTPUT
+ prime-cache:
+ name: prime-cache
+ runs-on: ubuntu-latest
+ needs: [pre-ci]
+ if: needs.pre-ci.outputs.should-build-be == 'true' || needs.pre-ci.outputs.should-build-fe == 'true' || needs.pre-ci.outputs.should-build-pkg == 'true' || needs.pre-ci.outputs.assets-json == 'true' || contains(github.event.pull_request.labels.*.name, 'force-full-ci')
+ steps:
+
+ - name: Checkout pnpm-lock
+ uses: actions/checkout@v4
+ with:
+ sparse-checkout: |
+ pnpm-lock.yaml
+
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: ${{ env.PNPM_VERSION }}
+
+ - name: Get pnpm store directory
+ shell: bash
+ run: |
+ echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
+
+ - name: Cache node modules
+ id: cache-pnpm
+ uses: actions/cache@v4
+ env:
+ cache-name: node-modules
+ with:
+ path: ${{ env.STORE_PATH }}
+ key: ${{ runner.os }}-${{ env.NODE_VERSION }}-build-${{ env.cache-name }}-${{ hashFiles('pnpm-lock.yaml') }}
+ lookup-only: true
+
+ - if: ${{ steps.cache-pnpm.outputs.cache-hit != 'true' }}
+ name: Full checkout
+ uses: actions/checkout@v4
+
+ - if: ${{ steps.cache-pnpm.outputs.cache-hit != 'true' }}
+ name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+
+ - if: ${{ steps.cache-pnpm.outputs.cache-hit != 'true' }}
+ name: Install dependencies
+ run: pnpm install
+
+ check-pretty:
+ name: check-pretty
+ needs: [pre-ci, prime-cache]
+ runs-on: ubuntu-latest
+ if: needs.pre-ci.outputs.should-build-be == 'true' || needs.pre-ci.outputs.should-build-fe == 'true' || needs.pre-ci.outputs.should-build-pkg == 'true' || needs.pre-ci.outputs.assets-json == 'true' || contains(github.event.pull_request.labels.*.name, 'force-full-ci')
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: ${{ env.PNPM_VERSION }}
+
+ - name: Install prettier
+ run: pnpm add -g prettier@2.5.1
+
+ - name: Get changed files
+ if: github.event_name == 'pull_request'
+ id: get-changed-files
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const changedFiles = await github.paginate(
+ github.rest.pulls.listFiles,
+ {
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: context.payload.pull_request.number,
+ }
+ );
+ return changedFiles.filter(file=> file.status !== "removed").map(file => file.filename).join(' ');
+
+ - name: Check pretty (changed files)
+ if: github.event_name == 'pull_request'
+ id: check-pretty
+ run: |
+ CHANGED_FILES=$(echo ${{ steps.get-changed-files.outputs.result }})
+ if [ -n "$CHANGED_FILES" ]; then
+ pnpm prettier --check $CHANGED_FILES
+ fi
+
+ - name: Check pretty (all files)
+ if: github.event_name == 'push'
+ run: pnpm prettier --check .
+
ci-be:
name: ci-be
- needs: [pre-ci]
+ needs: [pre-ci, prime-cache, check-pretty]
runs-on: ubuntu-latest
- if: needs.pre-ci.outputs.should-build-be == 'true'
+ if: needs.pre-ci.outputs.should-build-be == 'true' || needs.pre-ci.outputs.should-build-pkg == 'true' || contains(github.event.pull_request.labels.*.name, 'force-full-ci')
steps:
- uses: actions/checkout@v4
+ with:
+ sparse-checkout: |
+ backend
+ packages
+
- name: Set up Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- - name: Install dependencies
- run: npm ci & cd backend && npm ci
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: ${{ env.PNPM_VERSION }}
+
+ - name: Get pnpm store directory
+ shell: bash
+ run: |
+ echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- - name: Check pretty
- run: npm run pretty-code-be
+ - name: Cache node modules
+ id: cache-pnpm
+ uses: actions/cache@v4
+ env:
+ cache-name: node-modules
+ with:
+ path: ${{ env.STORE_PATH }}
+ key: ${{ runner.os }}-${{ env.NODE_VERSION }}-build-${{ env.cache-name }}-${{ hashFiles('pnpm-lock.yaml') }}
+
+ - name: Install dependencies
+ run: pnpm install
- name: Check lint
run: npm run lint-be
- name: Build
- run: npm run pr-check-build-be
+ run: npm run build-be
- name: Test
run: npm run test-be
ci-fe:
name: ci-fe
- needs: [pre-ci]
+ needs: [pre-ci, prime-cache, check-pretty]
runs-on: ubuntu-latest
- if: needs.pre-ci.outputs.should-build-fe == 'true'
+ if: needs.pre-ci.outputs.should-build-fe == 'true' || needs.pre-ci.outputs.should-build-pkg == 'true' || contains(github.event.pull_request.labels.*.name, 'force-full-ci')
steps:
+
- uses: actions/checkout@v4
+ with:
+ sparse-checkout: |
+ frontend
+ packages
+
- name: Set up Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
@@ -99,29 +235,50 @@ jobs:
working-directory: ./frontend/src/ts/constants
run: mv ./firebase-config-example.ts ./firebase-config.ts && cp ./firebase-config.ts ./firebase-config-live.ts
- - name: Install dependencies
- run: npm ci & cd frontend && npm ci
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: ${{ env.PNPM_VERSION }}
- - name: Check pretty
- run: npm run pretty-code-fe
+ - name: Get pnpm store directory
+ shell: bash
+ run: |
+ echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
+
+ - name: Cache node modules
+ id: cache-pnpm
+ uses: actions/cache@v4
+ env:
+ cache-name: node-modules
+ with:
+ path: ${{ env.STORE_PATH }}
+ key: ${{ runner.os }}-${{ env.NODE_VERSION }}-build-${{ env.cache-name }}-${{ hashFiles('pnpm-lock.yaml') }}
+
+ - name: Install dependencies
+ run: pnpm install
- name: Check lint
run: npm run lint-fe
- name: Build
- run: npm run pr-check-build-fe
+ run: npm run build-fe
- name: Test
run: npm run test-fe
ci-assets:
name: ci-assets
- needs: [pre-ci]
+ needs: [pre-ci, prime-cache, check-pretty]
runs-on: ubuntu-latest
- if: needs.pre-ci.outputs.assets-json == 'true'
+ if: needs.pre-ci.outputs.assets-json == 'true' || contains(github.event.pull_request.labels.*.name, 'force-full-ci')
steps:
- uses: actions/checkout@v4
- - uses: dorny/paths-filter@v2
+ with:
+ sparse-checkout: |
+ frontend
+ packages
+
+ - uses: dorny/paths-filter@v3
id: filter
with:
filters: |
@@ -135,13 +292,33 @@ jobs:
- 'frontend/static/themes/*.json'
- 'frontend/static/challenges/*.json'
- 'frontend/static/layouts/*.json'
+
- name: Set up Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: ${{ env.PNPM_VERSION }}
+
+ - name: Get pnpm store directory
+ shell: bash
+ run: |
+ echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
+
+ - name: Cache node modules
+ id: cache-pnpm
+ uses: actions/cache@v4
+ env:
+ cache-name: node-modules
+ with:
+ path: ${{ env.STORE_PATH }}
+ key: ${{ runner.os }}-${{ env.NODE_VERSION }}-build-${{ env.cache-name }}-${{ hashFiles('pnpm-lock.yaml') }}
+
- name: Install dependencies
- run: npm ci & cd frontend && npm ci
+ run: pnpm install
- name: Lint JSON
run: npm run pr-check-lint-json
@@ -158,14 +335,61 @@ jobs:
if: steps.filter.outputs.other-json == 'true'
run: npm run pr-check-other-json
+ ci-pkg:
+ name: ci-pkg
+ needs: [pre-ci, prime-cache,check-pretty]
+ runs-on: ubuntu-latest
+ if: needs.pre-ci.outputs.should-build-pkg == 'true' || contains(github.event.pull_request.labels.*.name, 'force-full-ci')
+ steps:
+
+ - uses: actions/checkout@v4
+ with:
+ sparse-checkout: |
+ packages
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v4
+ with:
+ version: ${{ env.PNPM_VERSION }}
+
+ - name: Get pnpm store directory
+ shell: bash
+ run: |
+ echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
+
+ - name: Cache node modules
+ id: cache-pnpm
+ uses: actions/cache@v4
+ env:
+ cache-name: node-modules
+ with:
+ path: ${{ env.STORE_PATH }}
+ key: ${{ runner.os }}-${{ env.NODE_VERSION }}-build-${{ env.cache-name }}-${{ hashFiles('pnpm-lock.yaml') }}
+
+ - name: Install dependencies
+ run: pnpm install
+
+ - name: Check lint
+ run: npm run lint-pkg
+
+ - name: Build
+ run: npm run build-pkg
+
+ - name: Test
+ run: npm run test-pkg
+
on-failure:
permissions: write-all
name: on-failure
runs-on: ubuntu-latest
- needs: [ci-be, ci-fe, ci-assets]
+ needs: [ci-be, ci-fe, ci-assets, ci-pkg]
if: ${{ always() && contains(needs.*.result, 'failure') && github.ref != 'refs/heads/master' }}
steps:
- - uses: actions/checkout@v4
- name: Save the PR number in an artifact
shell: bash
env:
diff --git a/.github/workflows/pretty-fix.yml b/.github/workflows/pretty-fix.yml
index d24a7b824fdc..6669033ab3ef 100644
--- a/.github/workflows/pretty-fix.yml
+++ b/.github/workflows/pretty-fix.yml
@@ -21,9 +21,9 @@ jobs:
echo "PR_TITLE=Prettier Fix - $(date)" >> $GITHUB_ENV
- name: Set up Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v4
with:
- node-version: "18.19.1"
+ node-version: "20.16.0"
- name: Install dependencies
run: npm i prettier@2.5.1 --save-dev --save-exact
diff --git a/.github/workflows/publish-docker-images.yml b/.github/workflows/publish-docker-images.yml
index 9cd911ba3918..9c6bf3cb86fe 100644
--- a/.github/workflows/publish-docker-images.yml
+++ b/.github/workflows/publish-docker-images.yml
@@ -52,7 +52,7 @@ jobs:
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
repository: ${{ env.BE_REPO }}
short-description: Official backend server for monkeytype.com
- readme-filepath: ./SELF_HOSTING.md
+ readme-filepath: ./docs/SELF_HOSTING.md
- name: Frontend extract metadata (tags, labels)
id: femeta
@@ -79,4 +79,4 @@ jobs:
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
repository: ${{ env.FE_REPO }}
short-description: Official frontend server for monkeytype.com
- readme-filepath: ./SELF_HOSTING.md
+ readme-filepath: ./docs/SELF_HOSTING.md
diff --git a/.github/workflows/semantic-pr-title.yml b/.github/workflows/semantic-pr-title.yml
index 34a68910400b..d98760a4dd1f 100644
--- a/.github/workflows/semantic-pr-title.yml
+++ b/.github/workflows/semantic-pr-title.yml
@@ -6,6 +6,7 @@ on:
- opened
- edited
- synchronize
+ - reopened
permissions:
pull-requests: write
@@ -14,8 +15,10 @@ jobs:
main:
name: check
runs-on: ubuntu-latest
+ if: ${{ github.event.pull_request.user.login != 'dependabot[bot]' }}
steps:
- - uses: amannn/action-semantic-pull-request@v5
+ - name: Lint and verify PR title
+ uses: amannn/action-semantic-pull-request@v5
id: lint_pr_title
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -34,17 +37,26 @@ jobs:
style
test
requireScope: false
-
+ subjectPattern: ^.+ \(@[^ ,]+(, @[^ ,]+)*\)$
+ subjectPatternError: |
+ Title "{title}"
+ didn't match the configured pattern. Please ensure that the title
+ contains your name so that you can be credited in our changelog.
+ A correct version would look something like:
+
+ feat: add new feature (@github-username)
+ fix: resolve bug (@github-username)
+
- uses: marocchino/sticky-pull-request-comment@v2
# When the previous steps fails, the workflow would stop. By adding this
# condition you can continue the execution with the populated error message.
- if: always() && (steps.lint_pr_title.outputs.error_message != null)
+ if: always() && (steps.lint_pr_title.outputs.error_message != null)
with:
header: pr-title-lint-error
message: |
Hey there and thank you for opening this pull request! 👋🏼
- We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted.
+ We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and also include the author name at the end inside round brackets. It looks like your proposed title needs to be adjusted.
Details:
diff --git a/.gitignore b/.gitignore
index d1dfa0145400..eafaff45385c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -126,3 +126,5 @@ copyAnticheatToDev.sh
# ignore generated fonts
frontend/src/webfonts-generated
frontend/static/webfonts-preview
+
+.turbo
diff --git a/.husky/pre-push b/.husky/pre-push
index 3f6c7cabe272..d3e8d78dbc08 100755
--- a/.husky/pre-push
+++ b/.husky/pre-push
@@ -8,10 +8,10 @@ export NVM_DIR="$HOME/.nvm"
if [ $(git branch --no-color | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/') = "master" ] && [ $(git remote get-url origin) = "https://github.com/monkeytypegame/monkeytype" ]; then
nvm use
- echo "Running tests before pushing to master..."
- npm run test
+ echo "Running a full check before pushing to master..."
+ npm run full-check
if [ $? -ne 0 ]; then
- echo "Tests failed, aborting push."
+ echo "Full check failed, aborting push."
exit 1
fi
fi
\ No newline at end of file
diff --git a/.npmrc b/.npmrc
index d9ca8860b092..21769de75c9e 100644
--- a/.npmrc
+++ b/.npmrc
@@ -1,2 +1,3 @@
engine-strict=true
save-exact=true
+save-prefix=''
\ No newline at end of file
diff --git a/.nvmrc b/.nvmrc
index 34bfa5c695c0..593cb75bc5c2 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-18.19.1
\ No newline at end of file
+20.16.0
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
index 00df139f0a17..593cee690ba8 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,3 +1,7 @@
+.turbo
+.github
+*.md
+docker/
*.min.js
*.min.css
layouts.ts
@@ -7,8 +11,11 @@ sound/*
node_modules
css/balloon.css
_list.json
-backend/build
backend/logs
backend/coverage
backend/globalConfig.json
frontend/public
+dist/
+build/
+frontend/coverage
+pnpm*.yaml
diff --git a/.release-it-fe.json b/.release-it-fe.json
deleted file mode 100644
index 3613a4a74c70..000000000000
--- a/.release-it-fe.json
+++ /dev/null
@@ -1,31 +0,0 @@
-{
- "hooks": {
- "before:init": [
- "npm run lint-fe",
- "npm run test-fe",
- "cd frontend && npm run validate-json && npm run build"
- ],
- "before:release": [
- "cd frontend && npx firebase deploy -P live --only hosting",
- "sh ./bin/purgeCfCache.sh"
- ]
- },
- "git": {
- "commitMessage": "chore: release v${version}",
- "requireCleanWorkingDir": false,
- "changelog": "node bin/buildChangelog.mjs"
- },
- "github": {
- "release": true
- },
- "npm": {
- "publish": false,
- "ignoreVersion": true
- },
- "plugins": {
- "@csmith/release-it-calver-plugin": {
- "format": "yy.ww.minor",
- "increment": "calendar.minor"
- }
- }
-}
diff --git a/.release-it.json b/.release-it.json
deleted file mode 100644
index 721dc0b11451..000000000000
--- a/.release-it.json
+++ /dev/null
@@ -1,32 +0,0 @@
-{
- "hooks": {
- "before:init": [
- "npm run lint",
- "npm run test",
- "cd frontend && npm run validate-json && npm run build"
- ],
- "before:release": [
- "sh ./bin/deployBackend.sh",
- "cd frontend && npx firebase deploy -P live --only hosting",
- "sh ./bin/purgeCfCache.sh"
- ]
- },
- "git": {
- "commitMessage": "chore: release v${version}",
- "requireCleanWorkingDir": false,
- "changelog": "node bin/buildChangelog.mjs"
- },
- "github": {
- "release": true
- },
- "npm": {
- "publish": false,
- "ignoreVersion": true
- },
- "plugins": {
- "@csmith/release-it-calver-plugin": {
- "format": "yy.ww.minor",
- "increment": "calendar.minor"
- }
- }
-}
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index de1f33f44b47..c354a1411cd2 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -2,6 +2,9 @@
"recommendations": [
"esbenp.prettier-vscode",
"vitest.explorer",
- "huntertran.auto-markdown-toc"
+ "huntertran.auto-markdown-toc",
+ "ms-vscode.vscode-typescript-next",
+ "dbaeumer.vscode-eslint",
+ "esbenp.prettier-vscode"
]
}
diff --git a/README.md b/README.md
index e6d46f613b0b..fa5aad3e984c 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,23 @@
[](https://monkeytype.com/)
-
-
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+
# About
@@ -33,11 +47,15 @@ If you encounter a bug or have a feature request, [send us an email](mailto:cont
# Want to Contribute?
-Refer to [CONTRIBUTING.md](./CONTRIBUTING.md).
+Refer to [CONTRIBUTING.md](./docs/CONTRIBUTING.md).
# Code of Conduct
-Before contributing to this repository, please read the [code of conduct](./CODE_OF_CONDUCT.md).
+Before contributing to this repository, please read the [code of conduct](./docs/CODE_OF_CONDUCT.md).
+
+# Security
+
+To report a security vulnerability, please refer to [SECURITY.md](./docs/SECURITY.md).
# Credits
diff --git a/backend/.eslintrc.cjs b/backend/.eslintrc.cjs
new file mode 100644
index 000000000000..278eceb29bc0
--- /dev/null
+++ b/backend/.eslintrc.cjs
@@ -0,0 +1,16 @@
+/** @type {import("eslint").Linter.Config} */
+module.exports = {
+ root: true,
+ extends: ["@monkeytype/eslint-config"],
+ ignorePatterns: [
+ "node_modules/",
+ "dist/",
+ "build/",
+ "__tests__/",
+ "jest.config.ts",
+ "__migration__/",
+ ],
+ rules: {
+ eqeqeq: "error",
+ },
+};
diff --git a/backend/__tests__/__testData__/auth.ts b/backend/__tests__/__testData__/auth.ts
new file mode 100644
index 000000000000..a1c0f94dead6
--- /dev/null
+++ b/backend/__tests__/__testData__/auth.ts
@@ -0,0 +1,37 @@
+import { Configuration } from "@monkeytype/contracts/schemas/configuration";
+import { randomBytes } from "crypto";
+import { hash } from "bcrypt";
+import { ObjectId } from "mongodb";
+import { base64UrlEncode } from "../../src/utils/misc";
+import * as ApeKeyDal from "../../src/dal/ape-keys";
+
+export async function mockAuthenticateWithApeKey(
+ uid: string,
+ config: Configuration
+): Promise {
+ if (!config.apeKeys.acceptKeys)
+ throw Error("config.apeKeys.acceptedKeys needs to be set to true");
+ const { apeKeyBytes, apeKeySaltRounds } = config.apeKeys;
+
+ const apiKey = randomBytes(apeKeyBytes).toString("base64url");
+ const saltyHash = await hash(apiKey, apeKeySaltRounds);
+
+ const apeKey: MonkeyTypes.ApeKeyDB = {
+ _id: new ObjectId(),
+ name: "bob",
+ enabled: true,
+ uid,
+ hash: saltyHash,
+ createdOn: Date.now(),
+ modifiedOn: Date.now(),
+ lastUsedOn: -1,
+ useCount: 0,
+ };
+
+ const apeKeyId = new ObjectId().toHexString();
+
+ vi.spyOn(ApeKeyDal, "getApeKey").mockResolvedValue(apeKey);
+ vi.spyOn(ApeKeyDal, "updateLastUsedOn").mockResolvedValue();
+
+ return base64UrlEncode(`${apeKeyId}.${apiKey}`);
+}
diff --git a/backend/__tests__/api/controllers/admin.spec.ts b/backend/__tests__/api/controllers/admin.spec.ts
new file mode 100644
index 000000000000..9cf789c9692b
--- /dev/null
+++ b/backend/__tests__/api/controllers/admin.spec.ts
@@ -0,0 +1,429 @@
+import request, { Test as SuperTest } from "supertest";
+import app from "../../../src/app";
+import { ObjectId } from "mongodb";
+import * as Configuration from "../../../src/init/configuration";
+import * as AdminUuidDal from "../../../src/dal/admin-uids";
+import * as UserDal from "../../../src/dal/user";
+import * as ReportDal from "../../../src/dal/report";
+import GeorgeQueue from "../../../src/queues/george-queue";
+import * as AuthUtil from "../../../src/utils/auth";
+import _ from "lodash";
+
+const mockApp = request(app);
+const configuration = Configuration.getCachedConfiguration();
+const uid = new ObjectId().toHexString();
+
+describe("ApeKeyController", () => {
+ const isAdminMock = vi.spyOn(AdminUuidDal, "isAdmin");
+
+ beforeEach(async () => {
+ isAdminMock.mockReset();
+ await enableAdminEndpoints(true);
+ isAdminMock.mockResolvedValue(true);
+ });
+
+ describe("check for admin", () => {
+ it("should succeed if user is admin", async () => {
+ //GIVEN
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/admin")
+ .set("authorization", `Uid ${uid}`)
+ .expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "OK",
+ data: null,
+ });
+
+ expect(isAdminMock).toHaveBeenCalledWith(uid);
+ });
+ it("should fail if user is no admin", async () => {
+ await expectFailForNonAdmin(
+ mockApp.get("/admin").set("authorization", `Uid ${uid}`)
+ );
+ });
+ it("should fail if admin endpoints are disabled", async () => {
+ await expectFailForDisabledEndpoint(
+ mockApp.get("/admin").set("authorization", `Uid ${uid}`)
+ );
+ });
+ });
+
+ describe("toggle ban", () => {
+ const userBannedMock = vi.spyOn(UserDal, "setBanned");
+ const georgeBannedMock = vi.spyOn(GeorgeQueue, "userBanned");
+ const getUserMock = vi.spyOn(UserDal, "getPartialUser");
+
+ beforeEach(() => {
+ [userBannedMock, georgeBannedMock, getUserMock].forEach((it) =>
+ it.mockReset()
+ );
+ });
+
+ it("should ban user with discordId", async () => {
+ //GIVEN
+ const victimUid = new ObjectId().toHexString();
+ getUserMock.mockResolvedValue({
+ banned: false,
+ discordId: "discordId",
+ } as any);
+
+ //WHEN
+ const { body } = await mockApp
+ .post("/admin/toggleBan")
+ .send({ uid: victimUid })
+ .set("authorization", `Uid ${uid}`)
+ .expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Ban toggled",
+ data: { banned: true },
+ });
+
+ expect(getUserMock).toHaveBeenCalledWith(victimUid, "toggle ban", [
+ "banned",
+ "discordId",
+ ]);
+ expect(userBannedMock).toHaveBeenCalledWith(victimUid, true);
+ expect(georgeBannedMock).toHaveBeenCalledWith("discordId", true);
+ });
+ it("should unban user without discordId", async () => {
+ //GIVEN
+ const victimUid = new ObjectId().toHexString();
+ getUserMock.mockResolvedValue({
+ banned: true,
+ } as any);
+
+ //WHEN
+ const { body } = await mockApp
+ .post("/admin/toggleBan")
+ .send({ uid: victimUid })
+ .set("authorization", `Uid ${uid}`)
+ .expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Ban toggled",
+ data: { banned: false },
+ });
+
+ expect(getUserMock).toHaveBeenCalledWith(victimUid, "toggle ban", [
+ "banned",
+ "discordId",
+ ]);
+ expect(userBannedMock).toHaveBeenCalledWith(victimUid, false);
+ expect(georgeBannedMock).not.toHaveBeenCalled();
+ });
+ it("should fail without mandatory properties", async () => {
+ //GIVEN
+
+ //WHEN
+ const { body } = await mockApp
+ .post("/admin/toggleBan")
+ .send({})
+ .set("authorization", `Uid ${uid}`)
+ .expect(422);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Invalid request data schema",
+ validationErrors: ['"uid" Required'],
+ });
+ });
+ it("should fail with unknown properties", async () => {
+ //GIVEN
+
+ //WHEN
+ const { body } = await mockApp
+ .post("/admin/toggleBan")
+ .send({ uid: new ObjectId().toHexString(), extra: "value" })
+ .set("authorization", `Uid ${uid}`)
+ .expect(422);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Invalid request data schema",
+ validationErrors: ["Unrecognized key(s) in object: 'extra'"],
+ });
+ });
+ it("should fail if user is no admin", async () => {
+ await expectFailForNonAdmin(
+ mockApp
+ .post("/admin/toggleBan")
+ .send({ uid: new ObjectId().toHexString() })
+ .set("authorization", `Uid ${uid}`)
+ );
+ });
+ it("should fail if admin endpoints are disabled", async () => {
+ //GIVEN
+ await expectFailForDisabledEndpoint(
+ mockApp
+ .post("/admin/toggleBan")
+ .send({ uid: new ObjectId().toHexString() })
+ .set("authorization", `Uid ${uid}`)
+ );
+ });
+ });
+ describe("accept reports", () => {
+ const getReportsMock = vi.spyOn(ReportDal, "getReports");
+ const deleteReportsMock = vi.spyOn(ReportDal, "deleteReports");
+ const addToInboxMock = vi.spyOn(UserDal, "addToInbox");
+
+ beforeEach(() => {
+ [getReportsMock, deleteReportsMock, addToInboxMock].forEach((it) =>
+ it.mockReset()
+ );
+ });
+
+ it("should accept reports", async () => {
+ //GIVEN
+ const reportOne = {
+ id: "1",
+ reason: "one",
+ } as any as MonkeyTypes.Report;
+ const reportTwo = {
+ id: "2",
+ reason: "two",
+ } as any as MonkeyTypes.Report;
+ getReportsMock.mockResolvedValue([reportOne, reportTwo]);
+
+ //WHEN
+ const { body } = await mockApp
+ .post("/admin/report/accept")
+ .send({
+ reports: [{ reportId: reportOne.id }, { reportId: reportTwo.id }],
+ })
+ .set("authorization", `Uid ${uid}`)
+ .expect(200);
+
+ expect(body).toEqual({
+ message: "Reports removed and users notified.",
+ data: null,
+ });
+
+ expect(addToInboxMock).toBeCalledTimes(2);
+ expect(deleteReportsMock).toHaveBeenCalledWith(["1", "2"]);
+ });
+ it("should fail wihtout mandatory properties", async () => {
+ //WHEN
+ const { body } = await mockApp
+ .post("/admin/report/accept")
+ .send({})
+ .set("authorization", `Uid ${uid}`)
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid request data schema",
+ validationErrors: ['"reports" Required'],
+ });
+ });
+ it("should fail with empty reports", async () => {
+ //WHEN
+ const { body } = await mockApp
+ .post("/admin/report/accept")
+ .send({ reports: [] })
+ .set("authorization", `Uid ${uid}`)
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid request data schema",
+ validationErrors: [
+ '"reports" Array must contain at least 1 element(s)',
+ ],
+ });
+ });
+ it("should fail with unknown properties", async () => {
+ //WHEN
+ const { body } = await mockApp
+ .post("/admin/report/accept")
+ .send({ reports: [{ reportId: "1", extra2: "value" }], extra: "value" })
+ .set("authorization", `Uid ${uid}`)
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid request data schema",
+ validationErrors: [
+ `"reports.0" Unrecognized key(s) in object: 'extra2'`,
+ "Unrecognized key(s) in object: 'extra'",
+ ],
+ });
+ });
+ it("should fail if user is no admin", async () => {
+ await expectFailForNonAdmin(
+ mockApp
+ .post("/admin/report/accept")
+ .send({ reports: [] })
+ .set("authorization", `Uid ${uid}`)
+ );
+ });
+ it("should fail if admin endpoints are disabled", async () => {
+ //GIVEN
+ await expectFailForDisabledEndpoint(
+ mockApp
+ .post("/admin/report/accept")
+ .send({ reports: [] })
+ .set("authorization", `Uid ${uid}`)
+ );
+ });
+ });
+ describe("reject reports", () => {
+ const getReportsMock = vi.spyOn(ReportDal, "getReports");
+ const deleteReportsMock = vi.spyOn(ReportDal, "deleteReports");
+ const addToInboxMock = vi.spyOn(UserDal, "addToInbox");
+
+ beforeEach(() => {
+ [getReportsMock, deleteReportsMock, addToInboxMock].forEach((it) =>
+ it.mockReset()
+ );
+ });
+
+ it("should reject reports", async () => {
+ //GIVEN
+ const reportOne = {
+ id: "1",
+ reason: "one",
+ } as any as MonkeyTypes.Report;
+ const reportTwo = {
+ id: "2",
+ reason: "two",
+ } as any as MonkeyTypes.Report;
+ getReportsMock.mockResolvedValue([reportOne, reportTwo]);
+
+ //WHEN
+ const { body } = await mockApp
+ .post("/admin/report/reject")
+ .send({
+ reports: [
+ { reportId: reportOne.id, reason: "test" },
+ { reportId: reportTwo.id },
+ ],
+ })
+ .set("authorization", `Uid ${uid}`)
+ .expect(200);
+
+ expect(body).toEqual({
+ message: "Reports removed and users notified.",
+ data: null,
+ });
+
+ expect(addToInboxMock).toHaveBeenCalledTimes(2);
+ expect(deleteReportsMock).toHaveBeenCalledWith(["1", "2"]);
+ });
+ it("should fail wihtout mandatory properties", async () => {
+ //WHEN
+ const { body } = await mockApp
+ .post("/admin/report/reject")
+ .send({})
+ .set("authorization", `Uid ${uid}`)
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid request data schema",
+ validationErrors: ['"reports" Required'],
+ });
+ });
+ it("should fail with empty reports", async () => {
+ //WHEN
+ const { body } = await mockApp
+ .post("/admin/report/reject")
+ .send({ reports: [] })
+ .set("authorization", `Uid ${uid}`)
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid request data schema",
+ validationErrors: [
+ '"reports" Array must contain at least 1 element(s)',
+ ],
+ });
+ });
+ it("should fail with unknown properties", async () => {
+ //WHEN
+ const { body } = await mockApp
+ .post("/admin/report/reject")
+ .send({ reports: [{ reportId: "1", extra2: "value" }], extra: "value" })
+ .set("authorization", `Uid ${uid}`)
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid request data schema",
+ validationErrors: [
+ `"reports.0" Unrecognized key(s) in object: 'extra2'`,
+ "Unrecognized key(s) in object: 'extra'",
+ ],
+ });
+ });
+ it("should fail if user is no admin", async () => {
+ await expectFailForNonAdmin(
+ mockApp
+ .post("/admin/report/reject")
+ .send({ reports: [] })
+ .set("authorization", `Uid ${uid}`)
+ );
+ });
+ it("should fail if admin endpoints are disabled", async () => {
+ //GIVEN
+ await expectFailForDisabledEndpoint(
+ mockApp
+ .post("/admin/report/reject")
+ .send({ reports: [] })
+ .set("authorization", `Uid ${uid}`)
+ );
+ });
+ });
+ describe("send forgot password email", () => {
+ const sendForgotPasswordEmailMock = vi.spyOn(
+ AuthUtil,
+ "sendForgotPasswordEmail"
+ );
+
+ beforeEach(() => {
+ sendForgotPasswordEmailMock.mockReset();
+ });
+
+ it("should send forgot password link", async () => {
+ //GIVEN
+
+ //WHEN
+ const { body } = await mockApp
+ .post("/admin/sendForgotPasswordEmail")
+ .send({ email: "meowdec@example.com" })
+ .set("authorization", `Uid ${uid}`)
+ .expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Password reset request email sent.",
+ data: null,
+ });
+
+ expect(sendForgotPasswordEmailMock).toHaveBeenCalledWith(
+ "meowdec@example.com"
+ );
+ });
+ });
+
+ async function expectFailForNonAdmin(call: SuperTest): Promise {
+ isAdminMock.mockResolvedValue(false);
+ const { body } = await call.expect(403);
+ expect(body.message).toEqual("You don't have permission to do this.");
+ }
+ async function expectFailForDisabledEndpoint(call: SuperTest): Promise {
+ await enableAdminEndpoints(false);
+ const { body } = await call.expect(503);
+ expect(body.message).toEqual("Admin endpoints are currently disabled.");
+ }
+});
+async function enableAdminEndpoints(enabled: boolean): Promise {
+ const mockConfig = _.merge(await configuration, {
+ admin: { endpointsEnabled: enabled },
+ });
+
+ vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
+ mockConfig
+ );
+}
diff --git a/backend/__tests__/api/controllers/ape-key.spec.ts b/backend/__tests__/api/controllers/ape-key.spec.ts
new file mode 100644
index 000000000000..6061a4e99d88
--- /dev/null
+++ b/backend/__tests__/api/controllers/ape-key.spec.ts
@@ -0,0 +1,374 @@
+import request, { Test as SuperTest } from "supertest";
+import app from "../../../src/app";
+import * as ApeKeyDal from "../../../src/dal/ape-keys";
+import { ObjectId } from "mongodb";
+import * as Configuration from "../../../src/init/configuration";
+import * as UserDal from "../../../src/dal/user";
+import _ from "lodash";
+
+const mockApp = request(app);
+const configuration = Configuration.getCachedConfiguration();
+const uid = new ObjectId().toHexString();
+
+describe("ApeKeyController", () => {
+ const getUserMock = vi.spyOn(UserDal, "getPartialUser");
+
+ beforeEach(async () => {
+ await enableApeKeysEndpoints(true);
+ getUserMock.mockResolvedValue(user(uid, {}));
+ vi.useFakeTimers();
+ vi.setSystemTime(1000);
+ });
+
+ afterEach(() => {
+ getUserMock.mockReset();
+ vi.useRealTimers();
+ });
+
+ describe("get ape keys", () => {
+ const getApeKeysMock = vi.spyOn(ApeKeyDal, "getApeKeys");
+
+ afterEach(() => {
+ getApeKeysMock.mockReset();
+ });
+
+ it("should get the users config", async () => {
+ //GIVEN
+ const keyOne = apeKeyDb(uid);
+ const keyTwo = apeKeyDb(uid);
+ getApeKeysMock.mockResolvedValue([keyOne, keyTwo]);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/ape-keys")
+ .set("authorization", `Uid ${uid}`)
+ .expect(200);
+
+ //THEN
+ expect(body).toHaveProperty("message", "ApeKeys retrieved");
+ expect(body.data).toHaveProperty(keyOne._id.toHexString(), {
+ name: keyOne.name,
+ enabled: keyOne.enabled,
+ createdOn: keyOne.createdOn,
+ modifiedOn: keyOne.modifiedOn,
+ lastUsedOn: keyOne.lastUsedOn,
+ });
+ expect(body.data).toHaveProperty(keyTwo._id.toHexString(), {
+ name: keyTwo.name,
+ enabled: keyTwo.enabled,
+ createdOn: keyTwo.createdOn,
+ modifiedOn: keyTwo.modifiedOn,
+ lastUsedOn: keyTwo.lastUsedOn,
+ });
+ expect(body.data).keys([keyOne._id, keyTwo._id]);
+
+ expect(getApeKeysMock).toHaveBeenCalledWith(uid);
+ });
+ it("should fail if apeKeys endpoints are disabled", async () => {
+ await expectFailForDisabledEndpoint(
+ mockApp.get("/ape-keys").set("authorization", `Uid ${uid}`)
+ );
+ });
+ it("should fail if user has no apeKey permissions", async () => {
+ await expectFailForNoPermissions(
+ mockApp.get("/ape-keys").set("authorization", `Uid ${uid}`)
+ );
+ });
+ });
+
+ describe("add ape key", () => {
+ const addApeKeyMock = vi.spyOn(ApeKeyDal, "addApeKey");
+ const countApeKeysMock = vi.spyOn(ApeKeyDal, "countApeKeysForUser");
+
+ beforeEach(() => {
+ countApeKeysMock.mockResolvedValue(0);
+ });
+
+ afterEach(() => {
+ addApeKeyMock.mockReset();
+ countApeKeysMock.mockReset();
+ });
+
+ it("should add ape key", async () => {
+ //GIVEN
+ addApeKeyMock.mockResolvedValue("1");
+
+ //WHEN
+ const { body } = await mockApp
+ .post("/ape-keys")
+ .set("authorization", `Uid ${uid}`)
+ .send({ name: "test", enabled: true })
+ .expect(200);
+
+ expect(body.message).toEqual("ApeKey generated");
+ expect(body.data).keys("apeKey", "apeKeyDetails", "apeKeyId");
+ expect(body.data.apeKey).not.toBeNull();
+
+ expect(body.data.apeKeyDetails).toStrictEqual({
+ createdOn: 1000,
+ enabled: true,
+ lastUsedOn: -1,
+ modifiedOn: 1000,
+ name: "test",
+ });
+
+ expect(body.data.apeKeyId).toEqual("1");
+
+ expect(addApeKeyMock).toHaveBeenCalledWith(
+ expect.objectContaining({
+ createdOn: 1000,
+ enabled: true,
+ lastUsedOn: -1,
+ modifiedOn: 1000,
+ name: "test",
+ uid: uid,
+ useCount: 0,
+ })
+ );
+ });
+ it("should fail without mandatory properties", async () => {
+ //WHEN
+ const { body } = await mockApp
+ .post("/ape-keys")
+ .send({})
+ .set("authorization", `Uid ${uid}`)
+ .expect(422);
+
+ //THEN
+ expect(body).toStrictEqual({
+ message: "Invalid request data schema",
+ validationErrors: [`"name" Required`, `"enabled" Required`],
+ });
+ });
+ it("should fail with extra properties", async () => {
+ //WHEN
+ const { body } = await mockApp
+ .post("/ape-keys")
+ .send({ name: "test", enabled: true, extra: "value" })
+ .set("authorization", `Uid ${uid}`)
+ .expect(422);
+
+ //THEN
+ expect(body).toStrictEqual({
+ message: "Invalid request data schema",
+ validationErrors: ["Unrecognized key(s) in object: 'extra'"],
+ });
+ });
+
+ it("should fail if max apeKeys is reached", async () => {
+ //GIVEN
+ countApeKeysMock.mockResolvedValue(1);
+
+ //WHEN
+ const { body } = await mockApp
+ .post("/ape-keys")
+ .send({ name: "test", enabled: false })
+ .set("authorization", `Uid ${uid}`)
+ .expect(409);
+
+ //THEN
+ expect(body.message).toEqual(
+ "Maximum number of ApeKeys have been generated"
+ );
+ });
+ it("should fail if apeKeys endpoints are disabled", async () => {
+ await expectFailForDisabledEndpoint(
+ mockApp
+ .post("/ape-keys")
+ .send({ name: "test", enabled: false })
+ .set("authorization", `Uid ${uid}`)
+ );
+ });
+ it("should fail if user has no apeKey permissions", async () => {
+ await expectFailForNoPermissions(
+ mockApp
+ .post("/ape-keys")
+ .send({ name: "test", enabled: false })
+ .set("authorization", `Uid ${uid}`)
+ );
+ });
+ });
+
+ describe("edit ape key", () => {
+ const editApeKeyMock = vi.spyOn(ApeKeyDal, "editApeKey");
+ const apeKeyId = new ObjectId().toHexString();
+
+ afterEach(() => {
+ editApeKeyMock.mockReset();
+ });
+
+ it("should edit ape key", async () => {
+ //GIVEN
+ editApeKeyMock.mockResolvedValue();
+
+ //WHEN
+ const { body } = await mockApp
+ .patch(`/ape-keys/${apeKeyId}`)
+ .send({ name: "new", enabled: false })
+ .set("authorization", `Uid ${uid}`)
+ .expect(200);
+
+ //THEN
+ expect(body.message).toEqual("ApeKey updated");
+ expect(editApeKeyMock).toHaveBeenCalledWith(uid, apeKeyId, "new", false);
+ });
+ it("should edit ape key with single property", async () => {
+ //GIVEN
+ editApeKeyMock.mockResolvedValue();
+
+ //WHEN
+ const { body } = await mockApp
+ .patch(`/ape-keys/${apeKeyId}`)
+ .send({ name: "new" })
+ .set("authorization", `Uid ${uid}`)
+ .expect(200);
+
+ //THEN
+ expect(body.message).toEqual("ApeKey updated");
+ expect(editApeKeyMock).toHaveBeenCalledWith(
+ uid,
+ apeKeyId,
+ "new",
+ undefined
+ );
+ });
+ it("should fail with missing path", async () => {
+ //GIVEN
+
+ //WHEN
+ await mockApp
+ .patch(`/ape-keys/`)
+ .set("authorization", `Uid ${uid}`)
+ .expect(404);
+ });
+ it("should fail with extra properties", async () => {
+ //GIVEN
+
+ //WHEN
+ const { body } = await mockApp
+ .patch(`/ape-keys/${apeKeyId}`)
+ .send({ name: "new", extra: "value" })
+ .set("authorization", `Uid ${uid}`)
+ .expect(422);
+
+ //THEN
+ expect(body).toStrictEqual({
+ message: "Invalid request data schema",
+ validationErrors: ["Unrecognized key(s) in object: 'extra'"],
+ });
+ });
+ it("should fail if apeKeys endpoints are disabled", async () => {
+ await expectFailForDisabledEndpoint(
+ mockApp
+ .patch(`/ape-keys/${apeKeyId}`)
+ .send({ name: "test", enabled: false })
+ .set("authorization", `Uid ${uid}`)
+ );
+ });
+ it("should fail if user has no apeKey permissions", async () => {
+ await expectFailForNoPermissions(
+ mockApp
+ .patch(`/ape-keys/${apeKeyId}`)
+ .send({ name: "test", enabled: false })
+ .set("authorization", `Uid ${uid}`)
+ );
+ });
+ });
+ describe("delete ape key", () => {
+ const deleteApeKeyMock = vi.spyOn(ApeKeyDal, "deleteApeKey");
+ const apeKeyId = new ObjectId().toHexString();
+
+ afterEach(() => {
+ deleteApeKeyMock.mockReset();
+ });
+
+ it("should delete ape key", async () => {
+ //GIVEN
+
+ deleteApeKeyMock.mockResolvedValue();
+ //WHEN
+ const { body } = await mockApp
+ .delete(`/ape-keys/${apeKeyId}`)
+ .set("authorization", `Uid ${uid}`)
+ .expect(200);
+
+ //THEN
+ expect(body.message).toEqual("ApeKey deleted");
+ expect(deleteApeKeyMock).toHaveBeenCalledWith(uid, apeKeyId);
+ });
+ it("should fail with missing path", async () => {
+ //GIVEN
+
+ //WHEN
+ await mockApp
+ .delete(`/ape-keys/`)
+ .set("authorization", `Uid ${uid}`)
+ .expect(404);
+ });
+ it("should fail if apeKeys endpoints are disabled", async () => {
+ await expectFailForDisabledEndpoint(
+ mockApp
+ .delete(`/ape-keys/${apeKeyId}`)
+ .set("authorization", `Uid ${uid}`)
+ );
+ });
+
+ it("should fail if user has no apeKey permissions", async () => {
+ await expectFailForNoPermissions(
+ mockApp
+ .delete(`/ape-keys/${apeKeyId}`)
+ .set("authorization", `Uid ${uid}`)
+ );
+ });
+ });
+ async function expectFailForNoPermissions(call: SuperTest): Promise {
+ getUserMock.mockResolvedValue(user(uid, { canManageApeKeys: false }));
+ const { body } = await call.expect(403);
+ expect(body.message).toEqual(
+ "You have lost access to ape keys, please contact support"
+ );
+ }
+ async function expectFailForDisabledEndpoint(call: SuperTest): Promise {
+ await enableApeKeysEndpoints(false);
+ const { body } = await call.expect(503);
+ expect(body.message).toEqual("ApeKeys are currently disabled.");
+ }
+});
+
+function apeKeyDb(
+ uid: string,
+ data?: Partial
+): MonkeyTypes.ApeKeyDB {
+ return {
+ _id: new ObjectId(),
+ uid,
+ hash: "hash",
+ useCount: 1,
+ name: "name",
+ enabled: true,
+ createdOn: Math.random() * Date.now(),
+ lastUsedOn: Math.random() * Date.now(),
+ modifiedOn: Math.random() * Date.now(),
+ ...data,
+ };
+}
+
+async function enableApeKeysEndpoints(enabled: boolean): Promise {
+ const mockConfig = _.merge(await configuration, {
+ apeKeys: { endpointsEnabled: enabled, maxKeysPerUser: 1 },
+ });
+
+ vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
+ mockConfig
+ );
+}
+
+function user(
+ uid: string,
+ data: Partial
+): MonkeyTypes.DBUser {
+ return {
+ uid,
+ ...data,
+ } as MonkeyTypes.DBUser;
+}
diff --git a/backend/__tests__/api/controllers/config.spec.ts b/backend/__tests__/api/controllers/config.spec.ts
new file mode 100644
index 000000000000..a3c50fb6882b
--- /dev/null
+++ b/backend/__tests__/api/controllers/config.spec.ts
@@ -0,0 +1,132 @@
+import request from "supertest";
+import app from "../../../src/app";
+import * as ConfigDal from "../../../src/dal/config";
+import { ObjectId } from "mongodb";
+const mockApp = request(app);
+
+describe("ConfigController", () => {
+ describe("get config", () => {
+ const getConfigMock = vi.spyOn(ConfigDal, "getConfig");
+
+ afterEach(() => {
+ getConfigMock.mockReset();
+ });
+
+ it("should get the users config", async () => {
+ //GIVEN
+ getConfigMock.mockResolvedValue({
+ _id: new ObjectId(),
+ uid: "123456789",
+ config: { language: "english" },
+ });
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/configs")
+ .set("authorization", "Uid 123456789")
+ .expect(200);
+
+ //THEN
+ expect(body).toStrictEqual({
+ message: "Configuration retrieved",
+ data: { language: "english" },
+ });
+
+ expect(getConfigMock).toHaveBeenCalledWith("123456789");
+ });
+ });
+ describe("update config", () => {
+ const saveConfigMock = vi.spyOn(ConfigDal, "saveConfig");
+
+ afterEach(() => {
+ saveConfigMock.mockReset();
+ });
+
+ it("should update the users config", async () => {
+ //GIVEN
+ saveConfigMock.mockResolvedValue({} as any);
+
+ //WHEN
+ const { body } = await mockApp
+ .patch("/configs")
+ .set("authorization", "Uid 123456789")
+ .accept("application/json")
+ .send({ language: "english" })
+ .expect(200);
+
+ //THEN
+ expect(body).toStrictEqual({
+ message: "Config updated",
+ data: null,
+ });
+
+ expect(saveConfigMock).toHaveBeenCalledWith("123456789", {
+ language: "english",
+ });
+ });
+ it("should fail with unknown config", async () => {
+ //WHEN
+ const { body } = await mockApp
+ .patch("/configs")
+ .set("authorization", "Uid 123456789")
+ .accept("application/json")
+ .send({ unknownValue: "unknown" })
+ .expect(422);
+
+ //THEN
+ expect(body).toStrictEqual({
+ message: "Invalid request data schema",
+ validationErrors: [`Unrecognized key(s) in object: 'unknownValue'`],
+ });
+
+ expect(saveConfigMock).not.toHaveBeenCalled();
+ });
+ it("should fail with invalid configs", async () => {
+ //WHEN
+ const { body } = await mockApp
+ .patch("/configs")
+ .set("authorization", "Uid 123456789")
+ .accept("application/json")
+ .send({ autoSwitchTheme: "yes", confidenceMode: "pretty" })
+ .expect(422);
+
+ //THEN
+ expect(body).toStrictEqual({
+ message: "Invalid request data schema",
+ validationErrors: [
+ `"autoSwitchTheme" Expected boolean, received string`,
+ `"confidenceMode" Invalid enum value. Expected 'off' | 'on' | 'max', received 'pretty'`,
+ ],
+ });
+
+ expect(saveConfigMock).not.toHaveBeenCalled();
+ });
+ });
+ describe("delete config", () => {
+ const deleteConfigMock = vi.spyOn(ConfigDal, "deleteConfig");
+
+ afterEach(() => {
+ deleteConfigMock.mockReset();
+ });
+
+ it("should delete the users config", async () => {
+ //GIVEN
+ deleteConfigMock.mockResolvedValue();
+
+ //WHEN
+
+ const { body } = await mockApp
+ .delete("/configs")
+ .set("authorization", "Uid 123456789")
+ .expect(200);
+
+ //THEN
+ expect(body).toStrictEqual({
+ message: "Config deleted",
+ data: null,
+ });
+
+ expect(deleteConfigMock).toHaveBeenCalledWith("123456789");
+ });
+ });
+});
diff --git a/backend/__tests__/api/controllers/configuration.spec.ts b/backend/__tests__/api/controllers/configuration.spec.ts
new file mode 100644
index 000000000000..e5cc0fb81660
--- /dev/null
+++ b/backend/__tests__/api/controllers/configuration.spec.ts
@@ -0,0 +1,194 @@
+import request from "supertest";
+import app from "../../../src/app";
+import {
+ BASE_CONFIGURATION,
+ CONFIGURATION_FORM_SCHEMA,
+} from "../../../src/constants/base-configuration";
+import * as Configuration from "../../../src/init/configuration";
+import type { Configuration as ConfigurationType } from "@monkeytype/contracts/schemas/configuration";
+import { ObjectId } from "mongodb";
+import * as Misc from "../../../src/utils/misc";
+import { DecodedIdToken } from "firebase-admin/auth";
+import * as AuthUtils from "../../../src/utils/auth";
+import * as AdminUuids from "../../../src/dal/admin-uids";
+
+const mockApp = request(app);
+const uid = new ObjectId().toHexString();
+const mockDecodedToken = {
+ uid,
+ email: "newuser@mail.com",
+ iat: 0,
+} as DecodedIdToken;
+
+describe("Configuration Controller", () => {
+ const isDevEnvironmentMock = vi.spyOn(Misc, "isDevEnvironment");
+ const verifyIdTokenMock = vi.spyOn(AuthUtils, "verifyIdToken");
+ const isAdminMock = vi.spyOn(AdminUuids, "isAdmin");
+
+ beforeEach(() => {
+ isAdminMock.mockReset();
+ verifyIdTokenMock.mockReset();
+ isDevEnvironmentMock.mockReset();
+
+ isDevEnvironmentMock.mockReturnValue(true);
+ isAdminMock.mockResolvedValue(true);
+ });
+
+ describe("getConfiguration", () => {
+ it("should get without authentication", async () => {
+ //GIVEN
+
+ //WHEN
+ const { body } = await mockApp.get("/configuration").expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Configuration retrieved",
+ data: BASE_CONFIGURATION,
+ });
+ });
+ });
+
+ describe("getConfigurationSchema", () => {
+ it("should get without authentication on dev", async () => {
+ //GIVEN
+
+ //WHEN
+ const { body } = await mockApp.get("/configuration/schema").expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Configuration schema retrieved",
+ data: CONFIGURATION_FORM_SCHEMA,
+ });
+ });
+
+ it("should fail without authentication on prod", async () => {
+ //GIVEN
+ isDevEnvironmentMock.mockReturnValue(false);
+
+ //WHEN
+ await mockApp.get("/configuration/schema").expect(401);
+ });
+ it("should get with authentication on prod", async () => {
+ //GIVEN
+ isDevEnvironmentMock.mockReturnValue(false);
+ verifyIdTokenMock.mockResolvedValue(mockDecodedToken);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/configuration/schema")
+ .set("Authorization", "Bearer 123456789")
+ .expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Configuration schema retrieved",
+ data: CONFIGURATION_FORM_SCHEMA,
+ });
+
+ expect(verifyIdTokenMock).toHaveBeenCalled();
+ });
+ it("should fail with non-admin user on prod", async () => {
+ //GIVEN
+ isDevEnvironmentMock.mockReturnValue(false);
+ verifyIdTokenMock.mockResolvedValue(mockDecodedToken);
+ isAdminMock.mockResolvedValue(false);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/configuration/schema")
+ .set("Authorization", "Bearer 123456789")
+ .expect(403);
+
+ //THEN
+ expect(body.message).toEqual("You don't have permission to do this.");
+ expect(verifyIdTokenMock).toHaveBeenCalled();
+ expect(isAdminMock).toHaveBeenCalledWith(uid);
+ });
+ });
+
+ describe("updateConfiguration", () => {
+ const patchConfigurationMock = vi.spyOn(
+ Configuration,
+ "patchConfiguration"
+ );
+ beforeEach(() => {
+ patchConfigurationMock.mockReset();
+ patchConfigurationMock.mockResolvedValue(true);
+ });
+
+ it("should update without authentication on dev", async () => {
+ //GIVEN
+ const patch = {
+ users: {
+ premium: {
+ enabled: true,
+ },
+ },
+ } as Partial;
+
+ //WHEN
+ const { body } = await mockApp
+ .patch("/configuration")
+ .send({ configuration: patch })
+ .expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Configuration updated",
+ data: null,
+ });
+
+ expect(patchConfigurationMock).toHaveBeenCalledWith(patch);
+ });
+
+ it("should fail update without authentication on prod", async () => {
+ //GIVEN
+ isDevEnvironmentMock.mockReturnValue(false);
+
+ //WHEN
+ await request(app)
+ .patch("/configuration")
+ .send({ configuration: {} })
+ .expect(401);
+
+ //THEN
+ expect(patchConfigurationMock).not.toHaveBeenCalled();
+ });
+ it("should update with authentication on prod", async () => {
+ //GIVEN
+ isDevEnvironmentMock.mockReturnValue(false);
+ verifyIdTokenMock.mockResolvedValue(mockDecodedToken);
+
+ //WHEN
+ await mockApp
+ .patch("/configuration")
+ .set("Authorization", "Bearer 123456789")
+ .send({ configuration: {} })
+ .expect(200);
+
+ //THEN
+ expect(patchConfigurationMock).toHaveBeenCalled();
+ expect(verifyIdTokenMock).toHaveBeenCalled();
+ });
+
+ it("should fail for non admin users on prod", async () => {
+ //GIVEN
+ isDevEnvironmentMock.mockReturnValue(false);
+ isAdminMock.mockResolvedValue(false);
+ verifyIdTokenMock.mockResolvedValue(mockDecodedToken);
+
+ //WHEN
+ await mockApp
+ .patch("/configuration")
+ .set("Authorization", "Bearer 123456789")
+ .send({ configuration: {} })
+ .expect(403);
+
+ //THEN
+ expect(patchConfigurationMock).not.toHaveBeenCalled();
+ expect(isAdminMock).toHaveBeenCalledWith(uid);
+ });
+ });
+});
diff --git a/backend/__tests__/api/controllers/dev.spec.ts b/backend/__tests__/api/controllers/dev.spec.ts
new file mode 100644
index 000000000000..0ac16cab2065
--- /dev/null
+++ b/backend/__tests__/api/controllers/dev.spec.ts
@@ -0,0 +1,59 @@
+import request from "supertest";
+import app from "../../../src/app";
+
+import { ObjectId } from "mongodb";
+import * as Misc from "../../../src/utils/misc";
+
+const uid = new ObjectId().toHexString();
+const mockApp = request(app);
+
+describe("DevController", () => {
+ describe("generate testData", () => {
+ const isDevEnvironmentMock = vi.spyOn(Misc, "isDevEnvironment");
+
+ beforeEach(() => {
+ isDevEnvironmentMock.mockReset();
+ isDevEnvironmentMock.mockReturnValue(true);
+ });
+
+ it("should fail on prod", async () => {
+ //GIVEN
+ isDevEnvironmentMock.mockReturnValue(false);
+ //WHEN
+ const { body } = await mockApp
+ .post("/dev/generateData")
+ .send({ username: "test" })
+ .expect(503);
+ //THEN
+ expect(body.message).toEqual(
+ "Development endpoints are only available in DEV mode."
+ );
+ });
+ it("should fail without mandatory properties", async () => {
+ //WHEN
+ const { body } = await mockApp
+ .post("/dev/generateData")
+ .send({})
+ .expect(422);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Invalid request data schema",
+ validationErrors: [`"username" Required`],
+ });
+ });
+ it("should fail with unknown properties", async () => {
+ //WHEN
+ const { body } = await mockApp
+ .post("/dev/generateData")
+ .send({ username: "Bob", extra: "value" })
+ .expect(422);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Invalid request data schema",
+ validationErrors: ["Unrecognized key(s) in object: 'extra'"],
+ });
+ });
+ });
+});
diff --git a/backend/__tests__/api/controllers/leaderboard.spec.ts b/backend/__tests__/api/controllers/leaderboard.spec.ts
index 00fc042a8232..c2f7bef451b8 100644
--- a/backend/__tests__/api/controllers/leaderboard.spec.ts
+++ b/backend/__tests__/api/controllers/leaderboard.spec.ts
@@ -1,35 +1,1117 @@
+import _ from "lodash";
+import { ObjectId } from "mongodb";
import request from "supertest";
import app from "../../../src/app";
+import * as LeaderboardDal from "../../../src/dal/leaderboards";
+import * as DailyLeaderboards from "../../../src/utils/daily-leaderboards";
+import * as WeeklyXpLeaderboard from "../../../src/services/weekly-xp-leaderboard";
import * as Configuration from "../../../src/init/configuration";
+import { mockAuthenticateWithApeKey } from "../../__testData__/auth";
+import {
+ LeaderboardEntry,
+ XpLeaderboardEntry,
+ XpLeaderboardRank,
+} from "@monkeytype/contracts/schemas/leaderboards";
const mockApp = request(app);
+const configuration = Configuration.getCachedConfiguration();
+const uid = new ObjectId().toHexString();
-describe("leaderboards controller test", () => {
- it("GET /leaderboards/xp/weekly", async () => {
- const configSpy = vi
- .spyOn(Configuration, "getCachedConfiguration")
- .mockResolvedValue({
- leaderboards: {
- weeklyXp: {
- enabled: true,
- expirationTimeInDays: 15,
- xpRewardBrackets: [],
- },
+const allModes = [
+ "10",
+ "25",
+ "50",
+ "100",
+ "15",
+ "30",
+ "60",
+ "120",
+ "zen",
+ "custom",
+];
+
+describe("Loaderboard Controller", () => {
+ describe("get leaderboard", () => {
+ const getLeaderboardMock = vi.spyOn(LeaderboardDal, "get");
+
+ beforeEach(() => {
+ getLeaderboardMock.mockReset();
+ });
+
+ it("should get for english time 60", async () => {
+ //GIVEN
+
+ const resultData = [
+ {
+ wpm: 20,
+ acc: 90,
+ timestamp: 1000,
+ raw: 92,
+ consistency: 80,
+ uid: "user1",
+ name: "user1",
+ discordId: "discordId",
+ discordAvatar: "discordAvatar",
+ rank: 1,
+ badgeId: 1,
+ isPremium: true,
+ },
+ {
+ wpm: 10,
+ acc: 80,
+ timestamp: 1200,
+ raw: 82,
+ uid: "user2",
+ name: "user2",
+ rank: 2,
+ },
+ ];
+ const mockData = resultData.map((it) => ({ ...it, _id: new ObjectId() }));
+ getLeaderboardMock.mockResolvedValue(mockData);
+
+ //WHEN
+
+ const { body } = await mockApp
+ .get("/leaderboards")
+ .query({ language: "english", mode: "time", mode2: "60" })
+ .expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Leaderboard retrieved",
+ data: resultData,
+ });
+
+ expect(getLeaderboardMock).toHaveBeenCalledWith(
+ "time",
+ "60",
+ "english",
+ 0,
+ 50
+ );
+ });
+
+ it("should get for english time 60 with skip and limit", async () => {
+ //GIVEN
+ getLeaderboardMock.mockResolvedValue([]);
+ const skip = 23;
+ const limit = 42;
+
+ //WHEN
+
+ const { body } = await mockApp
+ .get("/leaderboards")
+ .query({
+ language: "english",
+ mode: "time",
+ mode2: "60",
+ skip,
+ limit,
+ })
+ .expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Leaderboard retrieved",
+ data: [],
+ });
+
+ expect(getLeaderboardMock).toHaveBeenCalledWith(
+ "time",
+ "60",
+ "english",
+ skip,
+ limit
+ );
+ });
+
+ it("should get for mode", async () => {
+ getLeaderboardMock.mockResolvedValue([]);
+ for (const mode of ["time", "words", "quote", "zen", "custom"]) {
+ const response = await mockApp
+ .get("/leaderboards")
+ .query({ language: "english", mode, mode2: "custom" });
+ expect(response.status, "for mode " + mode).toEqual(200);
+ }
+ });
+
+ it("should get for mode2", async () => {
+ getLeaderboardMock.mockResolvedValue([]);
+ for (const mode2 of allModes) {
+ const response = await mockApp.get("/leaderboards").query({
+ language: "english",
+ mode: "words",
+ mode2,
+ });
+
+ expect(response.status, "for mode2 " + mode2).toEqual(200);
+ }
+ });
+ it("fails for missing query", async () => {
+ const { body } = await mockApp.get("/leaderboards").expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: [
+ '"language" Required',
+ '"mode" Required',
+ '"mode2" Needs to be either a number, "zen" or "custom."',
+ ],
+ });
+ });
+ it("fails for invalid query", async () => {
+ const { body } = await mockApp
+ .get("/leaderboards")
+ .query({
+ language: "en?gli.sh",
+ mode: "unknownMode",
+ mode2: "unknownMode2",
+ skip: -1,
+ limit: 100,
+ })
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: [
+ '"language" Can only contain letters [a-zA-Z0-9_+]',
+ `"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
+ '"mode2" Needs to be a number or a number represented as a string e.g. "10".',
+ '"skip" Number must be greater than or equal to 0',
+ '"limit" Number must be less than or equal to 50',
+ ],
+ });
+ });
+ it("fails for unknown query", async () => {
+ const { body } = await mockApp
+ .get("/leaderboards")
+ .query({
+ language: "english",
+ mode: "time",
+ mode2: "60",
+ extra: "value",
+ })
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: ["Unrecognized key(s) in object: 'extra'"],
+ });
+ });
+ it("fails while leaderboard is updating", async () => {
+ //GIVEN
+ getLeaderboardMock.mockResolvedValue(false);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/leaderboards")
+ .query({
+ language: "english",
+ mode: "time",
+ mode2: "60",
+ })
+ .expect(503);
+
+ expect(body.message).toEqual(
+ "Leaderboard is currently updating. Please try again in a few seconds."
+ );
+ });
+ });
+
+ describe("get rank", () => {
+ const getLeaderboardRankMock = vi.spyOn(LeaderboardDal, "getRank");
+
+ afterEach(() => {
+ getLeaderboardRankMock.mockReset();
+ });
+
+ it("fails withouth authentication", async () => {
+ await mockApp
+ .get("/leaderboards/rank")
+ .query({ language: "english", mode: "time", mode2: "60" })
+ .expect(401);
+ });
+
+ it("should get for english time 60", async () => {
+ //GIVEN
+
+ const entryId = new ObjectId();
+ const resultEntry = {
+ _id: entryId.toHexString(),
+ wpm: 10,
+ acc: 80,
+ timestamp: 1200,
+ raw: 82,
+ uid: "user2",
+ name: "user2",
+ rank: 2,
+ };
+ getLeaderboardRankMock.mockResolvedValue({
+ count: 1000,
+ rank: 50,
+ entry: resultEntry,
+ });
+
+ //WHEN
+
+ const { body } = await mockApp
+ .get("/leaderboards/rank")
+ .query({ language: "english", mode: "time", mode2: "60" })
+ .set("authorization", `Uid ${uid}`)
+ .expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Rank retrieved",
+ data: {
+ count: 1000,
+ rank: 50,
+ entry: resultEntry,
+ },
+ });
+
+ expect(getLeaderboardRankMock).toHaveBeenCalledWith(
+ "time",
+ "60",
+ "english",
+ uid
+ );
+ });
+ it("should get with ape key", async () => {
+ await acceptApeKeys(true);
+ const apeKey = await mockAuthenticateWithApeKey(uid, await configuration);
+
+ await mockApp
+ .get("/leaderboards/rank")
+ .query({ language: "english", mode: "time", mode2: "60" })
+ .set("authorization", "ApeKey " + apeKey)
+ .expect(200);
+ });
+ it("should get for mode", async () => {
+ getLeaderboardRankMock.mockResolvedValue({} as any);
+ for (const mode of ["time", "words", "quote", "zen", "custom"]) {
+ const response = await mockApp
+ .get("/leaderboards/rank")
+ .set("authorization", `Uid ${uid}`)
+ .query({ language: "english", mode, mode2: "custom" });
+ expect(response.status, "for mode " + mode).toEqual(200);
+ }
+ });
+
+ it("should get for mode2", async () => {
+ getLeaderboardRankMock.mockResolvedValue({} as any);
+ for (const mode2 of allModes) {
+ const response = await mockApp
+ .get("/leaderboards/rank")
+ .set("authorization", `Uid ${uid}`)
+ .query({ language: "english", mode: "words", mode2 });
+
+ expect(response.status, "for mode2 " + mode2).toEqual(200);
+ }
+ });
+ it("fails for missing query", async () => {
+ const { body } = await mockApp
+ .get("/leaderboards/rank")
+ .set("authorization", `Uid ${uid}`)
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: [
+ '"language" Required',
+ '"mode" Required',
+ '"mode2" Needs to be either a number, "zen" or "custom."',
+ ],
+ });
+ });
+ it("fails for invalid query", async () => {
+ const { body } = await mockApp
+ .get("/leaderboards/rank")
+ .query({
+ language: "en?gli.sh",
+ mode: "unknownMode",
+ mode2: "unknownMode2",
+ })
+ .set("authorization", `Uid ${uid}`)
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: [
+ '"language" Can only contain letters [a-zA-Z0-9_+]',
+ `"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
+ '"mode2" Needs to be a number or a number represented as a string e.g. "10".',
+ ],
+ });
+ });
+ it("fails for unknown query", async () => {
+ const { body } = await mockApp
+ .get("/leaderboards/rank")
+ .query({
+ language: "english",
+ mode: "time",
+ mode2: "60",
+ extra: "value",
+ })
+ .set("authorization", `Uid ${uid}`)
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: ["Unrecognized key(s) in object: 'extra'"],
+ });
+ });
+ it("fails while leaderboard is updating", async () => {
+ //GIVEN
+ getLeaderboardRankMock.mockResolvedValue(false);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/leaderboards/rank")
+ .query({
+ language: "english",
+ mode: "time",
+ mode2: "60",
+ })
+ .set("authorization", `Uid ${uid}`)
+ .expect(503);
+
+ expect(body.message).toEqual(
+ "Leaderboard is currently updating. Please try again in a few seconds."
+ );
+ });
+ });
+
+ describe("get daily leaderboard", () => {
+ const getDailyLeaderboardMock = vi.spyOn(
+ DailyLeaderboards,
+ "getDailyLeaderboard"
+ );
+
+ beforeEach(async () => {
+ getDailyLeaderboardMock.mockReset();
+ vi.useFakeTimers();
+ vi.setSystemTime(1722606812000);
+ await dailyLeaderboardEnabled(true);
+
+ getDailyLeaderboardMock.mockReturnValue({
+ getResults: () => Promise.resolve([]),
+ } as any);
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it("should get for english time 60", async () => {
+ //GIVEN
+ const lbConf = (await configuration).dailyLeaderboards;
+ const premiumEnabled = (await configuration).users.premium.enabled;
+
+ const resultData: LeaderboardEntry[] = [
+ {
+ name: "user1",
+ rank: 1,
+ wpm: 20,
+ acc: 90,
+ timestamp: 1000,
+ raw: 92,
+ consistency: 80,
+ uid: "user1",
+ discordId: "discordId",
+ discordAvatar: "discordAvatar",
+ },
+ {
+ wpm: 10,
+ rank: 2,
+ acc: 80,
+ timestamp: 1200,
+ raw: 82,
+ consistency: 72,
+ uid: "user2",
+ name: "user2",
+ },
+ ];
+
+ const getResultMock = vi.fn();
+ getResultMock.mockResolvedValue(resultData);
+ getDailyLeaderboardMock.mockReturnValue({
+ getResults: getResultMock,
+ } as any);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/leaderboards/daily")
+ .query({ language: "english", mode: "time", mode2: "60" })
+ .expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Daily leaderboard retrieved",
+ data: resultData,
+ });
+
+ expect(getDailyLeaderboardMock).toHaveBeenCalledWith(
+ "english",
+ "time",
+ "60",
+ lbConf,
+ -1
+ );
+
+ expect(getResultMock).toHaveBeenCalledWith(0, 49, lbConf, premiumEnabled);
+ });
+
+ it("should get for english time 60 for yesterday", async () => {
+ //GIVEN
+ const lbConf = (await configuration).dailyLeaderboards;
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/leaderboards/daily")
+ .query({
+ language: "english",
+ mode: "time",
+ mode2: "60",
+ daysBefore: 1,
+ })
+ .expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Daily leaderboard retrieved",
+ data: [],
+ });
+
+ expect(getDailyLeaderboardMock).toHaveBeenCalledWith(
+ "english",
+ "time",
+ "60",
+ lbConf,
+ 1722470400000
+ );
+ });
+ it("should get for english time 60 with skip and limit", async () => {
+ //GIVEN
+ const lbConf = (await configuration).dailyLeaderboards;
+ const premiumEnabled = (await configuration).users.premium.enabled;
+ const limit = 23;
+ const skip = 42;
+
+ const getResultMock = vi.fn();
+ getResultMock.mockResolvedValue([]);
+ getDailyLeaderboardMock.mockReturnValue({
+ getResults: getResultMock,
+ } as any);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/leaderboards/daily")
+ .query({
+ language: "english",
+ mode: "time",
+ mode2: "60",
+ skip,
+ limit,
+ })
+ .expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Daily leaderboard retrieved",
+ data: [],
+ });
+
+ expect(getDailyLeaderboardMock).toHaveBeenCalledWith(
+ "english",
+ "time",
+ "60",
+ lbConf,
+ -1
+ );
+
+ expect(getResultMock).toHaveBeenCalledWith(
+ skip,
+ skip + limit - 1,
+ lbConf,
+ premiumEnabled
+ );
+ });
+
+ it("fails for daysBefore not one", async () => {
+ const { body } = await mockApp
+ .get("/leaderboards/daily")
+ .query({
+ language: "english",
+ mode: "time",
+ mode2: "60",
+ daysBefore: 2,
+ })
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: ['"daysBefore" Invalid literal value, expected 1'],
+ });
+ });
+
+ it("fails if daily leaderboards are disabled", async () => {
+ await dailyLeaderboardEnabled(false);
+
+ const { body } = await mockApp.get("/leaderboards/daily").expect(503);
+
+ expect(body.message).toEqual(
+ "Daily leaderboards are not available at this time."
+ );
+ });
+
+ it("should get for mode", async () => {
+ for (const mode of ["time", "words", "quote", "zen", "custom"]) {
+ const response = await mockApp
+ .get("/leaderboards/daily")
+ .query({ language: "english", mode, mode2: "custom" });
+ expect(response.status, "for mode " + mode).toEqual(200);
+ }
+ });
+
+ it("should get for mode2", async () => {
+ for (const mode2 of allModes) {
+ const response = await mockApp
+ .get("/leaderboards/daily")
+ .query({ language: "english", mode: "words", mode2 });
+
+ expect(response.status, "for mode2 " + mode2).toEqual(200);
+ }
+ });
+ it("fails for missing query", async () => {
+ const { body } = await mockApp.get("/leaderboards").expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: [
+ '"language" Required',
+ '"mode" Required',
+ '"mode2" Needs to be either a number, "zen" or "custom."',
+ ],
+ });
+ });
+ it("fails for invalid query", async () => {
+ const { body } = await mockApp
+ .get("/leaderboards/daily")
+ .query({
+ language: "en?gli.sh",
+ mode: "unknownMode",
+ mode2: "unknownMode2",
+ })
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: [
+ '"language" Can only contain letters [a-zA-Z0-9_+]',
+ `"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
+ '"mode2" Needs to be a number or a number represented as a string e.g. "10".',
+ ],
+ });
+ });
+ it("fails for unknown query", async () => {
+ const { body } = await mockApp
+ .get("/leaderboards/daily")
+ .query({
+ language: "english",
+ mode: "time",
+ mode2: "60",
+ extra: "value",
+ })
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: ["Unrecognized key(s) in object: 'extra'"],
+ });
+ });
+ it("fails while leaderboard is missing", async () => {
+ //GIVEN
+ getDailyLeaderboardMock.mockReturnValue(null);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/leaderboards/daily")
+ .query({
+ language: "english",
+ mode: "time",
+ mode2: "60",
+ })
+ .expect(404);
+
+ expect(body.message).toEqual(
+ "There is no daily leaderboard for this mode"
+ );
+ });
+ });
+
+ describe("get daily leaderboard rank", () => {
+ const getDailyLeaderboardMock = vi.spyOn(
+ DailyLeaderboards,
+ "getDailyLeaderboard"
+ );
+
+ beforeEach(async () => {
+ getDailyLeaderboardMock.mockReset();
+ vi.useFakeTimers();
+ vi.setSystemTime(1722606812000);
+ await dailyLeaderboardEnabled(true);
+
+ getDailyLeaderboardMock.mockReturnValue({
+ getRank: () => Promise.resolve({} as any),
+ } as any);
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it("fails withouth authentication", async () => {
+ await mockApp
+ .get("/leaderboards/daily/rank")
+
+ .query({ language: "english", mode: "time", mode2: "60" })
+ .expect(401);
+ });
+ it("should get for english time 60", async () => {
+ //GIVEN
+ const lbConf = (await configuration).dailyLeaderboards;
+ const rankData = {
+ min: 100,
+ count: 1000,
+ rank: 12,
+ entry: {
+ wpm: 10,
+ rank: 2,
+ acc: 80,
+ timestamp: 1200,
+ raw: 82,
+ consistency: 72,
+ uid: "user2",
+ name: "user2",
},
+ };
+
+ const getRankMock = vi.fn();
+ getRankMock.mockResolvedValue(rankData);
+ getDailyLeaderboardMock.mockReturnValue({
+ getRank: getRankMock,
} as any);
- const response = await mockApp
- .get("/leaderboards/xp/weekly")
- .set({
- Accept: "application/json",
- })
- .expect(200);
+ //WHEN
+ const { body } = await mockApp
+ .get("/leaderboards/daily/rank")
+ .set("authorization", `Uid ${uid}`)
+ .query({ language: "english", mode: "time", mode2: "60" })
+ .expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Daily leaderboard rank retrieved",
+ data: rankData,
+ });
+
+ expect(getDailyLeaderboardMock).toHaveBeenCalledWith(
+ "english",
+ "time",
+ "60",
+ lbConf,
+ -1
+ );
+
+ expect(getRankMock).toHaveBeenCalledWith(uid, lbConf);
+ });
+ it("fails if daily leaderboards are disabled", async () => {
+ await dailyLeaderboardEnabled(false);
+
+ const { body } = await mockApp
+ .get("/leaderboards/daily/rank")
+ .set("authorization", `Uid ${uid}`)
+ .expect(503);
+
+ expect(body.message).toEqual(
+ "Daily leaderboards are not available at this time."
+ );
+ });
+ it("should get for mode", async () => {
+ for (const mode of ["time", "words", "quote", "zen", "custom"]) {
+ const response = await mockApp
+ .get("/leaderboards/daily/rank")
+ .set("authorization", `Uid ${uid}`)
+ .query({ language: "english", mode, mode2: "custom" });
+ expect(response.status, "for mode " + mode).toEqual(200);
+ }
+ });
+ it("should get for mode2", async () => {
+ for (const mode2 of allModes) {
+ const response = await mockApp
+ .get("/leaderboards/daily/rank")
+ .set("authorization", `Uid ${uid}`)
+ .query({ language: "english", mode: "words", mode2 });
+
+ expect(response.status, "for mode2 " + mode2).toEqual(200);
+ }
+ });
+ it("fails for missing query", async () => {
+ const { body } = await mockApp
+ .get("/leaderboards/daily/rank")
+ .set("authorization", `Uid ${uid}`)
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: [
+ '"language" Required',
+ '"mode" Required',
+ '"mode2" Needs to be either a number, "zen" or "custom."',
+ ],
+ });
+ });
+ it("fails for invalid query", async () => {
+ const { body } = await mockApp
+ .get("/leaderboards/daily/rank")
+ .query({
+ language: "en?gli.sh",
+ mode: "unknownMode",
+ mode2: "unknownMode2",
+ })
+ .set("authorization", `Uid ${uid}`)
+ .expect(422);
- expect(response.body).toEqual({
- message: "Weekly xp leaderboard retrieved",
- data: [],
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: [
+ '"language" Can only contain letters [a-zA-Z0-9_+]',
+ `"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
+ '"mode2" Needs to be a number or a number represented as a string e.g. "10".',
+ ],
+ });
});
+ it("fails for unknown query", async () => {
+ const { body } = await mockApp
+ .get("/leaderboards/daily/rank")
+ .query({
+ language: "english",
+ mode: "time",
+ mode2: "60",
+ extra: "value",
+ })
+ .set("authorization", `Uid ${uid}`)
+ .expect(422);
- configSpy.mockRestore();
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: ["Unrecognized key(s) in object: 'extra'"],
+ });
+ });
+ it("fails while leaderboard is missing", async () => {
+ //GIVEN
+ getDailyLeaderboardMock.mockReturnValue(null);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/leaderboards/daily/rank")
+ .set("authorization", `Uid ${uid}`)
+ .query({
+ language: "english",
+ mode: "time",
+ mode2: "60",
+ })
+ .expect(404);
+
+ expect(body.message).toEqual(
+ "There is no daily leaderboard for this mode"
+ );
+ });
+ });
+
+ describe("get xp weekly leaderboard", () => {
+ const getXpWeeklyLeaderboardMock = vi.spyOn(WeeklyXpLeaderboard, "get");
+
+ beforeEach(async () => {
+ getXpWeeklyLeaderboardMock.mockReset();
+ vi.useFakeTimers();
+ vi.setSystemTime(1722606812000);
+ await weeklyLeaderboardEnabled(true);
+
+ getXpWeeklyLeaderboardMock.mockReturnValue({
+ getResults: () => Promise.resolve([]),
+ } as any);
+ });
+
+ afterEach(() => {
+ vi.useRealTimers();
+ });
+
+ it("should get", async () => {
+ //GIVEN
+ const lbConf = (await configuration).leaderboards.weeklyXp;
+
+ const resultData: XpLeaderboardEntry[] = [
+ {
+ totalXp: 100,
+ rank: 1,
+ timeTypedSeconds: 100,
+ uid: "user1",
+ name: "user1",
+ discordId: "discordId",
+ discordAvatar: "discordAvatar",
+ lastActivityTimestamp: 1000,
+ },
+ {
+ totalXp: 75,
+ rank: 2,
+ timeTypedSeconds: 200,
+ uid: "user2",
+ name: "user2",
+ discordId: "discordId2",
+ discordAvatar: "discordAvatar2",
+ lastActivityTimestamp: 2000,
+ },
+ ];
+
+ const getResultMock = vi.fn();
+ getResultMock.mockResolvedValue(resultData);
+ getXpWeeklyLeaderboardMock.mockReturnValue({
+ getResults: getResultMock,
+ } as any);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/leaderboards/xp/weekly")
+ .query({})
+ .expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Weekly xp leaderboard retrieved",
+ data: resultData,
+ });
+
+ expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(lbConf, -1);
+
+ expect(getResultMock).toHaveBeenCalledWith(0, 49, lbConf);
+ });
+
+ it("should get for last week", async () => {
+ //GIVEN
+ const lbConf = (await configuration).leaderboards.weeklyXp;
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/leaderboards/xp/weekly")
+ .query({
+ weeksBefore: 1,
+ })
+ .expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Weekly xp leaderboard retrieved",
+ data: [],
+ });
+
+ expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(
+ lbConf,
+ 1721606400000
+ );
+ });
+
+ it("should get with skip and limit", async () => {
+ //GIVEN
+ const lbConf = (await configuration).leaderboards.weeklyXp;
+ const limit = 23;
+ const skip = 42;
+
+ const getResultMock = vi.fn();
+ getResultMock.mockResolvedValue([]);
+ getXpWeeklyLeaderboardMock.mockReturnValue({
+ getResults: getResultMock,
+ } as any);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/leaderboards/xp/weekly")
+ .query({
+ skip,
+ limit,
+ })
+ .expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Weekly xp leaderboard retrieved",
+ data: [],
+ });
+
+ expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(lbConf, -1);
+
+ expect(getResultMock).toHaveBeenCalledWith(
+ skip,
+ skip + limit - 1,
+ lbConf
+ );
+ });
+
+ it("fails if daily leaderboards are disabled", async () => {
+ await weeklyLeaderboardEnabled(false);
+
+ const { body } = await mockApp.get("/leaderboards/xp/weekly").expect(503);
+
+ expect(body.message).toEqual(
+ "Weekly XP leaderboards are not available at this time."
+ );
+ });
+
+ it("fails for weeksBefore not one", async () => {
+ const { body } = await mockApp
+ .get("/leaderboards/xp/weekly")
+ .query({
+ weeksBefore: 2,
+ })
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: ['"weeksBefore" Invalid literal value, expected 1'],
+ });
+ });
+ it("fails for unknown query", async () => {
+ const { body } = await mockApp
+ .get("/leaderboards/xp/weekly")
+ .query({
+ extra: "value",
+ })
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: ["Unrecognized key(s) in object: 'extra'"],
+ });
+ });
+ it("fails while leaderboard is missing", async () => {
+ //GIVEN
+ getXpWeeklyLeaderboardMock.mockReturnValue(null);
+
+ //WHEN
+ const { body } = await mockApp.get("/leaderboards/xp/weekly").expect(404);
+
+ expect(body.message).toEqual("XP leaderboard for this week not found.");
+ });
+ });
+
+ describe("get xp weekly leaderboard rank", () => {
+ const getXpWeeklyLeaderboardMock = vi.spyOn(WeeklyXpLeaderboard, "get");
+
+ beforeEach(async () => {
+ getXpWeeklyLeaderboardMock.mockReset();
+ await weeklyLeaderboardEnabled(true);
+ });
+
+ it("fails withouth authentication", async () => {
+ await mockApp.get("/leaderboards/xp/weekly/rank").expect(401);
+ });
+
+ it("should get", async () => {
+ //GIVEN
+ const lbConf = (await configuration).leaderboards.weeklyXp;
+
+ const resultData: XpLeaderboardRank = {
+ totalXp: 100,
+ rank: 1,
+ count: 100,
+ timeTypedSeconds: 100,
+ uid: "user1",
+ name: "user1",
+ discordId: "discordId",
+ discordAvatar: "discordAvatar",
+ lastActivityTimestamp: 1000,
+ };
+ const getRankMock = vi.fn();
+ getRankMock.mockResolvedValue(resultData);
+ getXpWeeklyLeaderboardMock.mockReturnValue({
+ getRank: getRankMock,
+ } as any);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/leaderboards/xp/weekly/rank")
+ .set("authorization", `Uid ${uid}`)
+ .expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Weekly xp leaderboard rank retrieved",
+ data: resultData,
+ });
+
+ expect(getXpWeeklyLeaderboardMock).toHaveBeenCalledWith(lbConf, -1);
+
+ expect(getRankMock).toHaveBeenCalledWith(uid, lbConf);
+ });
+ it("fails if daily leaderboards are disabled", async () => {
+ await weeklyLeaderboardEnabled(false);
+
+ const { body } = await mockApp
+ .get("/leaderboards/xp/weekly/rank")
+ .set("authorization", `Uid ${uid}`)
+ .expect(503);
+
+ expect(body.message).toEqual(
+ "Weekly XP leaderboards are not available at this time."
+ );
+ });
+
+ it("fails while leaderboard is missing", async () => {
+ //GIVEN
+ getXpWeeklyLeaderboardMock.mockReturnValue(null);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/leaderboards/xp/weekly/rank")
+ .set("authorization", `Uid ${uid}`)
+ .query({
+ language: "english",
+ mode: "time",
+ mode2: "60",
+ })
+ .expect(404);
+
+ expect(body.message).toEqual("XP leaderboard for this week not found.");
+ });
});
});
+
+async function acceptApeKeys(enabled: boolean): Promise {
+ const mockConfig = _.merge(await configuration, {
+ apeKeys: { acceptKeys: enabled },
+ });
+
+ vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
+ mockConfig
+ );
+}
+
+async function dailyLeaderboardEnabled(enabled: boolean): Promise {
+ const mockConfig = _.merge(await configuration, {
+ dailyLeaderboards: { enabled: enabled },
+ });
+
+ vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
+ mockConfig
+ );
+}
+async function weeklyLeaderboardEnabled(enabled: boolean): Promise {
+ const mockConfig = _.merge(await configuration, {
+ leaderboards: { weeklyXp: { enabled } },
+ });
+
+ vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
+ mockConfig
+ );
+}
diff --git a/backend/__tests__/api/controllers/preset.spec.ts b/backend/__tests__/api/controllers/preset.spec.ts
new file mode 100644
index 000000000000..90cc7c7c4834
--- /dev/null
+++ b/backend/__tests__/api/controllers/preset.spec.ts
@@ -0,0 +1,338 @@
+import request from "supertest";
+import app from "../../../src/app";
+import * as PresetDal from "../../../src/dal/preset";
+import { ObjectId } from "mongodb";
+const mockApp = request(app);
+
+describe("PresetController", () => {
+ describe("get presets", () => {
+ const getPresetsMock = vi.spyOn(PresetDal, "getPresets");
+
+ afterEach(() => {
+ getPresetsMock.mockReset();
+ });
+
+ it("should get the users presets", async () => {
+ //GIVEN
+ const presetOne = {
+ _id: new ObjectId(),
+ uid: "123456789",
+ name: "test1",
+ config: { language: "english" },
+ };
+ const presetTwo = {
+ _id: new ObjectId(),
+ uid: "123456789",
+ name: "test2",
+ config: { language: "polish" },
+ };
+
+ getPresetsMock.mockResolvedValue([presetOne, presetTwo]);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/presets")
+ .set("authorization", "Uid 123456789")
+ .expect(200);
+
+ //THEN
+ expect(body).toStrictEqual({
+ message: "Presets retrieved",
+ data: [
+ {
+ _id: presetOne._id.toHexString(),
+ name: "test1",
+ config: { language: "english" },
+ },
+ {
+ _id: presetTwo._id.toHexString(),
+ name: "test2",
+ config: { language: "polish" },
+ },
+ ],
+ });
+
+ expect(getPresetsMock).toHaveBeenCalledWith("123456789");
+ });
+ it("should return empty array if user has no presets", async () => {
+ //GIVEN
+ getPresetsMock.mockResolvedValue([]);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/presets")
+ .set("authorization", "Uid 123456789")
+ .expect(200);
+
+ //THEN
+ expect(body).toStrictEqual({
+ message: "Presets retrieved",
+ data: [],
+ });
+
+ expect(getPresetsMock).toHaveBeenCalledWith("123456789");
+ });
+ });
+
+ describe("add preset", () => {
+ const addPresetMock = vi.spyOn(PresetDal, "addPreset");
+
+ afterEach(() => {
+ addPresetMock.mockReset();
+ });
+
+ it("should add the users preset", async () => {
+ //GIVEN
+ addPresetMock.mockResolvedValue({ presetId: "1" });
+
+ //WHEN
+ const { body } = await mockApp
+ .post("/presets")
+ .set("authorization", "Uid 123456789")
+ .accept("application/json")
+ .send({
+ name: "new",
+ config: {
+ language: "english",
+ tags: ["one", "two"],
+ },
+ })
+ .expect(200);
+
+ //THEN
+ expect(body).toStrictEqual({
+ message: "Preset created",
+ data: { presetId: "1" },
+ });
+
+ expect(addPresetMock).toHaveBeenCalledWith("123456789", {
+ name: "new",
+ config: { language: "english", tags: ["one", "two"] },
+ });
+ });
+ it("should not fail with emtpy config", async () => {
+ //GIVEN
+
+ addPresetMock.mockResolvedValue({ presetId: "1" });
+
+ //WHEN
+ const { body } = await mockApp
+ .post("/presets")
+ .set("authorization", "Uid 123456789")
+ .accept("application/json")
+ .send({ name: "new", config: {} })
+ .expect(200);
+
+ //THEN
+ expect(body).toStrictEqual({
+ message: "Preset created",
+ data: { presetId: "1" },
+ });
+
+ expect(addPresetMock).toHaveBeenCalledWith("123456789", {
+ name: "new",
+ config: {},
+ });
+ });
+ it("should fail with missing mandatory properties", async () => {
+ //WHEN
+ const { body } = await mockApp
+ .post("/presets")
+ .set("authorization", "Uid 123456789")
+ .accept("application/json")
+ .send({})
+ .expect(422);
+
+ expect(body).toStrictEqual({
+ message: "Invalid request data schema",
+ validationErrors: [`"name" Required`, `"config" Required`],
+ });
+ expect(addPresetMock).not.toHaveBeenCalled();
+ });
+ it("should not fail with invalid preset", async () => {
+ //WHEN
+ const { body } = await mockApp
+ .post("/presets")
+ .set("authorization", "Uid 123456789")
+ .accept("application/json")
+ .send({
+ _id: "1",
+ name: "update",
+ extra: "extra",
+ config: {
+ extra: "extra",
+ autoSwitchTheme: "yes",
+ confidenceMode: "pretty",
+ },
+ })
+ .expect(422);
+
+ //THEN
+ expect(body).toStrictEqual({
+ message: "Invalid request data schema",
+ validationErrors: [
+ `"config.autoSwitchTheme" Expected boolean, received string`,
+ `"config.confidenceMode" Invalid enum value. Expected 'off' | 'on' | 'max', received 'pretty'`,
+ `"config" Unrecognized key(s) in object: 'extra'`,
+ `Unrecognized key(s) in object: '_id', 'extra'`,
+ ],
+ });
+
+ expect(addPresetMock).not.toHaveBeenCalled();
+ });
+ });
+
+ describe("update preset", () => {
+ const editPresetMock = vi.spyOn(PresetDal, "editPreset");
+
+ afterEach(() => {
+ editPresetMock.mockReset();
+ });
+
+ it("should update the users preset", async () => {
+ //GIVEN
+ editPresetMock.mockResolvedValue({} as any);
+
+ //WHEN
+ const { body } = await mockApp
+ .patch("/presets")
+ .set("authorization", "Uid 123456789")
+ .accept("application/json")
+ .send({
+ _id: "1",
+ name: "new",
+ config: {
+ language: "english",
+ tags: ["one", "two"],
+ },
+ })
+ .expect(200);
+
+ //THEN
+ expect(body).toStrictEqual({
+ message: "Preset updated",
+ data: null,
+ });
+
+ expect(editPresetMock).toHaveBeenCalledWith("123456789", {
+ _id: "1",
+ name: "new",
+ config: { language: "english", tags: ["one", "two"] },
+ });
+ });
+ it("should not fail with emtpy config", async () => {
+ //GIVEN
+
+ editPresetMock.mockResolvedValue({} as any);
+
+ //WHEN
+ const { body } = await mockApp
+ .patch("/presets")
+ .set("authorization", "Uid 123456789")
+ .accept("application/json")
+ .send({ _id: "1", name: "new", config: {} })
+ .expect(200);
+
+ //THEN
+ expect(body).toStrictEqual({
+ message: "Preset updated",
+ data: null,
+ });
+
+ expect(editPresetMock).toHaveBeenCalledWith("123456789", {
+ _id: "1",
+ name: "new",
+ config: {},
+ });
+ });
+ it("should fail with missing mandatory properties", async () => {
+ //WHEN
+ const { body } = await mockApp
+ .patch("/presets")
+ .set("authorization", "Uid 123456789")
+ .accept("application/json")
+ .send({})
+ .expect(422);
+
+ expect(body).toStrictEqual({
+ message: "Invalid request data schema",
+ validationErrors: [
+ `"_id" Required`,
+ `"name" Required`,
+ `"config" Required`,
+ ],
+ });
+ expect(editPresetMock).not.toHaveBeenCalled();
+ });
+ it("should not fail with invalid preset", async () => {
+ //WHEN
+ const { body } = await mockApp
+ .patch("/presets")
+ .set("authorization", "Uid 123456789")
+ .accept("application/json")
+ .send({
+ _id: "1",
+ name: "update",
+ extra: "extra",
+ config: {
+ extra: "extra",
+ autoSwitchTheme: "yes",
+ confidenceMode: "pretty",
+ },
+ })
+ .expect(422);
+
+ //THEN
+ expect(body).toStrictEqual({
+ message: "Invalid request data schema",
+ validationErrors: [
+ `"config.autoSwitchTheme" Expected boolean, received string`,
+ `"config.confidenceMode" Invalid enum value. Expected 'off' | 'on' | 'max', received 'pretty'`,
+ `"config" Unrecognized key(s) in object: 'extra'`,
+ `Unrecognized key(s) in object: 'extra'`,
+ ],
+ });
+
+ expect(editPresetMock).not.toHaveBeenCalled();
+ });
+ });
+ describe("delete config", () => {
+ const deletePresetMock = vi.spyOn(PresetDal, "removePreset");
+
+ afterEach(() => {
+ deletePresetMock.mockReset();
+ });
+
+ it("should delete the users preset", async () => {
+ //GIVEN
+ deletePresetMock.mockResolvedValue();
+
+ //WHEN
+
+ const { body } = await mockApp
+ .delete("/presets/1")
+ .set("authorization", "Uid 123456789")
+ .expect(200);
+
+ //THEN
+ expect(body).toStrictEqual({
+ message: "Preset deleted",
+ data: null,
+ });
+
+ expect(deletePresetMock).toHaveBeenCalledWith("123456789", "1");
+ });
+ it("should fail without preset _id", async () => {
+ //GIVEN
+ deletePresetMock.mockResolvedValue();
+
+ //WHEN
+ await mockApp
+ .delete("/presets/")
+ .set("authorization", "Uid 123456789")
+ .expect(404);
+
+ expect(deletePresetMock).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/backend/__tests__/api/controllers/psa.spec.ts b/backend/__tests__/api/controllers/psa.spec.ts
new file mode 100644
index 000000000000..9dcff487fda4
--- /dev/null
+++ b/backend/__tests__/api/controllers/psa.spec.ts
@@ -0,0 +1,80 @@
+import request from "supertest";
+import app from "../../../src/app";
+import * as PsaDal from "../../../src/dal/psa";
+import * as Prometheus from "../../../src/utils/prometheus";
+import { ObjectId } from "mongodb";
+const mockApp = request(app);
+
+describe("Psa Controller", () => {
+ describe("get psa", () => {
+ const getPsaMock = vi.spyOn(PsaDal, "get");
+ const recordClientVersionMock = vi.spyOn(Prometheus, "recordClientVersion");
+
+ afterEach(() => {
+ getPsaMock.mockReset();
+ recordClientVersionMock.mockReset();
+ });
+
+ it("get psas without authorization", async () => {
+ //GIVEN
+ const psaOne: PsaDal.DBPSA = {
+ _id: new ObjectId(),
+ message: "test2",
+ date: 1000,
+ level: 1,
+ sticky: true,
+ };
+ const psaTwo: PsaDal.DBPSA = {
+ _id: new ObjectId(),
+ message: "test2",
+ date: 2000,
+ level: 2,
+ sticky: false,
+ };
+ getPsaMock.mockResolvedValue([psaOne, psaTwo]);
+
+ //WHEN
+ const { body } = await mockApp.get("/psas").expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "PSAs retrieved",
+ data: [
+ {
+ _id: psaOne._id.toHexString(),
+ date: 1000,
+ level: 1,
+ message: "test2",
+ sticky: true,
+ },
+ {
+ _id: psaTwo._id.toHexString(),
+ date: 2000,
+ level: 2,
+ message: "test2",
+ sticky: false,
+ },
+ ],
+ });
+
+ expect(recordClientVersionMock).toHaveBeenCalledWith("unknown");
+ });
+ it("get psas with authorization", async () => {
+ await mockApp
+ .get("/psas")
+ .set("authorization", `Uid 123456789`)
+ .expect(200);
+ });
+
+ it("get psas records x-client-version", async () => {
+ await mockApp.get("/psas").set("x-client-version", "1.0").expect(200);
+
+ expect(recordClientVersionMock).toHaveBeenCalledWith("1.0");
+ });
+ it("get psas records client-version", async () => {
+ await mockApp.get("/psas").set("client-version", "2.0").expect(200);
+
+ expect(recordClientVersionMock).toHaveBeenCalledWith("2.0");
+ });
+ });
+});
diff --git a/backend/__tests__/api/controllers/public.spec.ts b/backend/__tests__/api/controllers/public.spec.ts
new file mode 100644
index 000000000000..9d9960e05bbd
--- /dev/null
+++ b/backend/__tests__/api/controllers/public.spec.ts
@@ -0,0 +1,144 @@
+import request from "supertest";
+import app from "../../../src/app";
+import * as PublicDal from "../../../src/dal/public";
+const mockApp = request(app);
+
+describe("PublicController", () => {
+ describe("get speed histogram", () => {
+ const getSpeedHistogramMock = vi.spyOn(PublicDal, "getSpeedHistogram");
+
+ afterEach(() => {
+ getSpeedHistogramMock.mockReset();
+ });
+
+ it("gets for english time 60", async () => {
+ //GIVEN
+ getSpeedHistogramMock.mockResolvedValue({ "0": 1, "10": 2 });
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/public/speedHistogram")
+ .query({ language: "english", mode: "time", mode2: "60" });
+ //.expect(200);
+ console.log(body);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Public speed histogram retrieved",
+ data: { "0": 1, "10": 2 },
+ });
+
+ expect(getSpeedHistogramMock).toHaveBeenCalledWith(
+ "english",
+ "time",
+ "60"
+ );
+ });
+
+ it("gets for mode", async () => {
+ for (const mode of ["time", "words", "quote", "zen", "custom"]) {
+ const response = await mockApp
+ .get("/public/speedHistogram")
+ .query({ language: "english", mode, mode2: "custom" });
+ expect(response.status, "for mode " + mode).toEqual(200);
+ }
+ });
+
+ it("gets for mode2", async () => {
+ for (const mode2 of [
+ "10",
+ "25",
+ "50",
+ "100",
+ "15",
+ "30",
+ "60",
+ "120",
+ "zen",
+ "custom",
+ ]) {
+ const response = await mockApp
+ .get("/public/speedHistogram")
+ .query({ language: "english", mode: "words", mode2 });
+
+ expect(response.status, "for mode2 " + mode2).toEqual(200);
+ }
+ });
+ it("fails for missing query", async () => {
+ const { body } = await mockApp.get("/public/speedHistogram").expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: [
+ '"language" Required',
+ '"mode" Required',
+ '"mode2" Needs to be either a number, "zen" or "custom."',
+ ],
+ });
+ });
+ it("fails for invalid query", async () => {
+ const { body } = await mockApp
+ .get("/public/speedHistogram")
+ .query({
+ language: "en?gli.sh",
+ mode: "unknownMode",
+ mode2: "unknownMode2",
+ })
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: [
+ '"language" Can only contain letters [a-zA-Z0-9_+]',
+ `"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
+ '"mode2" Needs to be a number or a number represented as a string e.g. "10".',
+ ],
+ });
+ });
+ it("fails for unknown query", async () => {
+ const { body } = await mockApp
+ .get("/public/speedHistogram")
+ .query({
+ language: "english",
+ mode: "time",
+ mode2: "60",
+ extra: "value",
+ })
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: ["Unrecognized key(s) in object: 'extra'"],
+ });
+ });
+ });
+ describe("get typing stats", () => {
+ const getTypingStatsMock = vi.spyOn(PublicDal, "getTypingStats");
+
+ afterEach(() => {
+ getTypingStatsMock.mockReset();
+ });
+
+ it("gets without authentication", async () => {
+ //GIVEN
+ getTypingStatsMock.mockResolvedValue({
+ testsCompleted: 23,
+ testsStarted: 42,
+ timeTyping: 1000,
+ } as any);
+
+ //WHEN
+ const { body } = await mockApp.get("/public/typingStats").expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Public typing stats retrieved",
+ data: {
+ testsCompleted: 23,
+ testsStarted: 42,
+ timeTyping: 1000,
+ },
+ });
+ });
+ });
+});
diff --git a/backend/__tests__/api/controllers/result.spec.ts b/backend/__tests__/api/controllers/result.spec.ts
index e656b09f1177..9e18514424b8 100644
--- a/backend/__tests__/api/controllers/result.spec.ts
+++ b/backend/__tests__/api/controllers/result.spec.ts
@@ -1,11 +1,14 @@
import request from "supertest";
import app from "../../../src/app";
-import _ from "lodash";
+import _, { omit } from "lodash";
import * as Configuration from "../../../src/init/configuration";
import * as ResultDal from "../../../src/dal/result";
import * as UserDal from "../../../src/dal/user";
+import * as LogsDal from "../../../src/dal/logs";
import * as AuthUtils from "../../../src/utils/auth";
import { DecodedIdToken } from "firebase-admin/lib/auth/token-verifier";
+import { ObjectId } from "mongodb";
+import { mockAuthenticateWithApeKey } from "../../__testData__/auth";
const uid = "123456";
const mockDecodedToken: DecodedIdToken = {
@@ -14,30 +17,69 @@ const mockDecodedToken: DecodedIdToken = {
iat: 0,
} as DecodedIdToken;
-vi.spyOn(AuthUtils, "verifyIdToken").mockResolvedValue(mockDecodedToken);
-
-const resultMock = vi.spyOn(ResultDal, "getResults");
-
const mockApp = request(app);
const configuration = Configuration.getCachedConfiguration();
describe("result controller test", () => {
+ const verifyIdTokenMock = vi.spyOn(AuthUtils, "verifyIdToken");
+
+ beforeEach(() => {
+ verifyIdTokenMock.mockReset();
+ verifyIdTokenMock.mockResolvedValue(mockDecodedToken);
+ });
+
describe("getResults", () => {
+ const resultMock = vi.spyOn(ResultDal, "getResults");
+
beforeEach(async () => {
resultMock.mockResolvedValue([]);
await enablePremiumFeatures(true);
+ vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(false);
});
+
afterEach(() => {
resultMock.mockReset();
});
- it("should get latest 1000 results for regular user", async () => {
+
+ it("should get results", async () => {
//GIVEN
- vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(false);
+ const resultOne = givenDbResult(uid);
+ const resultTwo = givenDbResult(uid);
+ resultMock.mockResolvedValue([resultOne, resultTwo]);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/results")
+ .set("Authorization", `Bearer ${uid}`)
+ .send()
+ .expect(200);
+
+ //THEN
+
+ expect(body.message).toEqual("Results retrieved");
+ expect(body.data).toEqual([
+ { ...resultOne, _id: resultOne._id.toHexString() },
+ { ...resultTwo, _id: resultTwo._id.toHexString() },
+ ]);
+ });
+ it("should get results with ape key", async () => {
+ //GIVEN
+ await acceptApeKeys(true);
+ const apeKey = await mockAuthenticateWithApeKey(uid, await configuration);
+
+ //WHEN
+ await mockApp
+ .get("/results")
+ .set("Authorization", `ApeKey ${apeKey}`)
+ .send()
+ .expect(200);
+ });
+ it("should get latest 1000 results for regular user", async () => {
//WHEN
await mockApp
.get("/results")
- .set("Authorization", "Bearer 123456789")
+ .set("Authorization", `Bearer ${uid}`)
.send()
.expect(200);
@@ -51,12 +93,11 @@ describe("result controller test", () => {
it("should get results filter by onOrAfterTimestamp", async () => {
//GIVEN
const now = Date.now();
- vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(false);
//WHEN
await mockApp
.get("/results")
.query({ onOrAfterTimestamp: now })
- .set("Authorization", "Bearer 123456789")
+ .set("Authorization", `Bearer ${uid}`)
.send()
.expect(200);
@@ -69,14 +110,11 @@ describe("result controller test", () => {
});
});
it("should get with limit and offset", async () => {
- //GIVEN
- vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(false);
-
//WHEN
await mockApp
.get("/results")
.query({ limit: 250, offset: 500 })
- .set("Authorization", "Bearer 123456789")
+ .set("Authorization", `Bearer ${uid}`)
.send()
.expect(200);
@@ -88,27 +126,20 @@ describe("result controller test", () => {
});
});
it("should fail exceeding max limit for regular user", async () => {
- //GIVEN
- vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(false);
-
//WHEN
- await mockApp
+ const { body } = await mockApp
.get("/results")
.query({ limit: 100, offset: 1000 })
- .set("Authorization", "Bearer 123456789")
+ .set("Authorization", `Bearer ${uid}`)
.send()
- .expect(422)
- .expect(
- expectErrorMessage(
- `Max results limit of ${
- (
- await configuration
- ).results.limits.regularUser
- } exceeded.`
- )
- );
+ .expect(422);
//THEN
+ expect(body.message).toEqual(
+ `Max results limit of ${
+ (await configuration).results.limits.regularUser
+ } exceeded.`
+ );
});
it("should get with higher max limit for premium user", async () => {
//GIVEN
@@ -118,7 +149,7 @@ describe("result controller test", () => {
await mockApp
.get("/results")
.query({ limit: 800, offset: 600 })
- .set("Authorization", "Bearer 123456789")
+ .set("Authorization", `Bearer ${uid}`)
.send()
.expect(200);
@@ -131,14 +162,11 @@ describe("result controller test", () => {
});
});
it("should get results if offset/limit is partly outside the max limit", async () => {
- //GIVEN
- vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(false);
-
//WHEN
await mockApp
.get("/results")
.query({ limit: 20, offset: 990 })
- .set("Authorization", "Bearer 123456789")
+ .set("Authorization", `Bearer ${uid}`)
.send()
.expect(200);
@@ -155,42 +183,36 @@ describe("result controller test", () => {
vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(false);
//WHEN
- await mockApp
+ const { body } = await mockApp
.get("/results")
.query({ limit: 2000 })
- .set("Authorization", "Bearer 123456789")
+ .set("Authorization", `Bearer ${uid}`)
.send()
- .expect(422)
- .expect(
- expectErrorMessage(
- '"limit" must be less than or equal to 1000 (2000)'
- )
- );
+ .expect(422);
//THEN
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: ['"limit" Number must be less than or equal to 1000'],
+ });
});
it("should fail exceeding maxlimit for premium user", async () => {
//GIVEN
vi.spyOn(UserDal, "checkIfUserIsPremium").mockResolvedValue(true);
//WHEN
- await mockApp
+ const { body } = await mockApp
.get("/results")
.query({ limit: 1000, offset: 25000 })
- .set("Authorization", "Bearer 123456789")
+ .set("Authorization", `Bearer ${uid}`)
.send()
- .expect(422)
- .expect(
- expectErrorMessage(
- `Max results limit of ${
- (
- await configuration
- ).results.limits.premiumUser
- } exceeded.`
- )
- );
-
+ .expect(422);
//THEN
+ expect(body.message).toEqual(
+ `Max results limit of ${
+ (await configuration).results.limits.premiumUser
+ } exceeded.`
+ );
});
it("should get results within regular limits for premium users even if premium is globally disabled", async () => {
//GIVEN
@@ -201,7 +223,7 @@ describe("result controller test", () => {
await mockApp
.get("/results")
.query({ limit: 100, offset: 900 })
- .set("Authorization", "Bearer 123456789")
+ .set("Authorization", `Bearer ${uid}`)
.send()
.expect(200);
@@ -218,15 +240,15 @@ describe("result controller test", () => {
enablePremiumFeatures(false);
//WHEN
- await mockApp
+ const { body } = await mockApp
.get("/results")
.query({ limit: 200, offset: 900 })
- .set("Authorization", "Bearer 123456789")
+ .set("Authorization", `Bearer ${uid}`)
.send()
- .expect(503)
- .expect(expectErrorMessage("Premium feature disabled."));
+ .expect(503);
//THEN
+ expect(body.message).toEqual("Premium feature disabled.");
});
it("should get results with regular limit as default for premium users if premium is globally disabled", async () => {
//GIVEN
@@ -236,7 +258,7 @@ describe("result controller test", () => {
//WHEN
await mockApp
.get("/results")
- .set("Authorization", "Bearer 123456789")
+ .set("Authorization", `Bearer ${uid}`)
.send()
.expect(200);
@@ -247,12 +269,524 @@ describe("result controller test", () => {
onOrAfterTimestamp: NaN,
});
});
+ it("should fail with unknown query parameters", async () => {
+ //WHEN
+ const { body } = await mockApp
+ .get("/results")
+ .query({ extra: "value" })
+ .set("Authorization", `Bearer ${uid}`)
+ .send()
+ .expect(422);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: ["Unrecognized key(s) in object: 'extra'"],
+ });
+ });
+ it("should get results with legacy values", async () => {
+ //GIVEN
+ const resultOne = givenDbResult(uid, {
+ charStats: undefined,
+ incorrectChars: 5,
+ correctChars: 12,
+ });
+ const resultTwo = givenDbResult(uid, {
+ charStats: undefined,
+ incorrectChars: 7,
+ correctChars: 15,
+ });
+ resultMock.mockResolvedValue([resultOne, resultTwo]);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/results")
+ .set("Authorization", `Bearer ${uid}`)
+ .send()
+ .expect(200);
+
+ //THEN
+
+ expect(body.message).toEqual("Results retrieved");
+ expect(body.data[0]).toMatchObject({
+ _id: resultOne._id.toHexString(),
+ charStats: [12, 5, 0, 0],
+ });
+ expect(body.data[0]).not.toHaveProperty("correctChars");
+ expect(body.data[0]).not.toHaveProperty("incorrectChars");
+
+ expect(body.data[1]).toMatchObject({
+ _id: resultTwo._id.toHexString(),
+ charStats: [15, 7, 0, 0],
+ });
+ expect(body.data[1]).not.toHaveProperty("correctChars");
+ expect(body.data[1]).not.toHaveProperty("incorrectChars");
+ });
});
-});
+ describe("getLastResult", () => {
+ const getLastResultMock = vi.spyOn(ResultDal, "getLastResult");
-function expectErrorMessage(message: string): (res: request.Response) => void {
- return (res) => expect(res.body).toHaveProperty("message", message);
-}
+ afterEach(() => {
+ getLastResultMock.mockReset();
+ });
+
+ it("should get last result", async () => {
+ //GIVEN
+ const result = givenDbResult(uid);
+ getLastResultMock.mockResolvedValue(result);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/results/last")
+ .set("Authorization", `Bearer ${uid}`)
+ .send()
+ .expect(200);
+
+ //THEN
+ expect(body.message).toEqual("Result retrieved");
+ expect(body.data).toEqual({ ...result, _id: result._id.toHexString() });
+ });
+ it("should get last result with ape key", async () => {
+ //GIVEN
+ await acceptApeKeys(true);
+ const apeKey = await mockAuthenticateWithApeKey(uid, await configuration);
+ const result = givenDbResult(uid);
+ getLastResultMock.mockResolvedValue(result);
+
+ //WHEN
+ await mockApp
+ .get("/results/last")
+ .set("Authorization", `ApeKey ${apeKey}`)
+ .send()
+ .expect(200);
+ });
+ it("should get last result with legacy values", async () => {
+ //GIVEN
+ const result = givenDbResult(uid, {
+ charStats: undefined,
+ incorrectChars: 5,
+ correctChars: 12,
+ });
+ getLastResultMock.mockResolvedValue(result);
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/results/last")
+ .set("Authorization", `Bearer ${uid}`)
+ .send()
+ .expect(200);
+
+ //THEN
+ expect(body.message).toEqual("Result retrieved");
+ expect(body.data).toMatchObject({
+ _id: result._id.toHexString(),
+ charStats: [12, 5, 0, 0],
+ });
+ expect(body.data).not.toHaveProperty("correctChars");
+ expect(body.data).not.toHaveProperty("incorrectChars");
+ });
+ });
+ describe("deleteAll", () => {
+ const deleteAllMock = vi.spyOn(ResultDal, "deleteAll");
+ const logToDbMock = vi.spyOn(LogsDal, "addLog");
+ afterEach(() => {
+ deleteAllMock.mockReset();
+ logToDbMock.mockReset();
+ });
+
+ it("should delete", async () => {
+ //GIVEN
+ verifyIdTokenMock.mockResolvedValue({
+ ...mockDecodedToken,
+ iat: Date.now() - 1000,
+ });
+ //WHEN
+ const { body } = await mockApp
+ .delete("/results")
+ .set("Authorization", `Bearer ${uid}`)
+ .send()
+ .expect(200);
+
+ //THEN
+ expect(body.message).toEqual("All results deleted");
+ expect(body.data).toBeNull();
+
+ expect(deleteAllMock).toHaveBeenCalledWith(uid);
+ expect(logToDbMock).toHaveBeenCalledWith("user_results_deleted", "", uid);
+ });
+ it("should fail to delete with non-fresh token", async () => {
+ await mockApp
+ .delete("/results")
+ .set("Authorization", `Bearer ${uid}`)
+ .send()
+ .expect(401);
+ });
+ });
+ describe("updateTags", () => {
+ const getResultMock = vi.spyOn(ResultDal, "getResult");
+ const updateTagsMock = vi.spyOn(ResultDal, "updateTags");
+ const getUserPartialMock = vi.spyOn(UserDal, "getPartialUser");
+ const checkIfTagPbMock = vi.spyOn(UserDal, "checkIfTagPb");
+
+ afterEach(() => {
+ [
+ getResultMock,
+ updateTagsMock,
+ getUserPartialMock,
+ checkIfTagPbMock,
+ ].forEach((it) => it.mockReset());
+ });
+
+ it("should update tags", async () => {
+ //GIVEN
+ const result = givenDbResult(uid);
+ const resultIdString = result._id.toHexString();
+ const tagIds = [
+ new ObjectId().toHexString(),
+ new ObjectId().toHexString(),
+ ];
+ const partialUser = { tags: [] };
+ getResultMock.mockResolvedValue(result);
+ updateTagsMock.mockResolvedValue({} as any);
+ getUserPartialMock.mockResolvedValue(partialUser as any);
+ checkIfTagPbMock.mockResolvedValue([]);
+
+ //WHEN
+ const { body } = await mockApp
+ .patch("/results/tags")
+ .set("Authorization", `Bearer ${uid}`)
+ .send({ resultId: resultIdString, tagIds })
+ .expect(200);
+
+ //THEN
+ expect(body.message).toEqual("Result tags updated");
+ expect(body.data).toEqual({
+ tagPbs: [],
+ });
+
+ expect(updateTagsMock).toHaveBeenCalledWith(uid, resultIdString, tagIds);
+ expect(getResultMock).toHaveBeenCalledWith(uid, resultIdString);
+ expect(getUserPartialMock).toHaveBeenCalledWith(uid, "update tags", [
+ "tags",
+ ]);
+ expect(checkIfTagPbMock).toHaveBeenCalledWith(uid, partialUser, result);
+ });
+ it("should apply defaults on missing data", async () => {
+ //GIVEN
+ const result = givenDbResult(uid);
+ const partialResult = omit(
+ result,
+ "difficulty",
+ "language",
+ "funbox",
+ "lazyMode",
+ "punctuation",
+ "numbers"
+ );
+
+ const resultIdString = result._id.toHexString();
+ const tagIds = [
+ new ObjectId().toHexString(),
+ new ObjectId().toHexString(),
+ ];
+ const partialUser = { tags: [] };
+ getResultMock.mockResolvedValue(partialResult);
+ updateTagsMock.mockResolvedValue({} as any);
+ getUserPartialMock.mockResolvedValue(partialUser as any);
+ checkIfTagPbMock.mockResolvedValue([]);
+
+ //WHEN
+ const { body } = await mockApp
+ .patch("/results/tags")
+ .set("Authorization", `Bearer ${uid}`)
+ .send({ resultId: resultIdString, tagIds })
+ .expect(200);
+
+ //THEN
+ expect(body.message).toEqual("Result tags updated");
+ expect(body.data).toEqual({
+ tagPbs: [],
+ });
+
+ expect(updateTagsMock).toHaveBeenCalledWith(uid, resultIdString, tagIds);
+ expect(getResultMock).toHaveBeenCalledWith(uid, resultIdString);
+ expect(getUserPartialMock).toHaveBeenCalledWith(uid, "update tags", [
+ "tags",
+ ]);
+ expect(checkIfTagPbMock).toHaveBeenCalledWith(uid, partialUser, {
+ ...result,
+ difficulty: "normal",
+ language: "english",
+ funbox: "none",
+ lazyMode: false,
+ punctuation: false,
+ numbers: false,
+ });
+ });
+ it("should fail with missing mandatory properties", async () => {
+ //GIVEN
+
+ //WHEN
+ const { body } = await mockApp
+ .patch("/results/tags")
+ .set("Authorization", `Bearer ${uid}`)
+ .send({})
+ .expect(422);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Invalid request data schema",
+ validationErrors: ['"tagIds" Required', '"resultId" Required'],
+ });
+ });
+ it("should fail with unknown properties", async () => {
+ //GIVEN
+
+ //WHEN
+ const { body } = await mockApp
+ .patch("/results/tags")
+ .set("Authorization", `Bearer ${uid}`)
+ .send({ extra: "value" })
+ .expect(422);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Invalid request data schema",
+ validationErrors: [
+ '"tagIds" Required',
+ '"resultId" Required',
+ "Unrecognized key(s) in object: 'extra'",
+ ],
+ });
+ });
+ });
+ describe("addResult", () => {
+ //TODO improve test coverage for addResult
+ const insertedId = new ObjectId();
+ const getUserMock = vi.spyOn(UserDal, "getUser");
+ const updateStreakMock = vi.spyOn(UserDal, "updateStreak");
+ const checkIfTagPbMock = vi.spyOn(UserDal, "checkIfTagPb");
+ const addResultMock = vi.spyOn(ResultDal, "addResult");
+
+ beforeEach(async () => {
+ await enableResultsSaving(true);
+
+ [getUserMock, updateStreakMock, checkIfTagPbMock, addResultMock].forEach(
+ (it) => it.mockReset()
+ );
+
+ getUserMock.mockResolvedValue({ name: "bob" } as any);
+ updateStreakMock.mockResolvedValue(0);
+ checkIfTagPbMock.mockResolvedValue([]);
+ addResultMock.mockResolvedValue({ insertedId });
+ });
+
+ it("should add result", async () => {
+ //GIVEN
+
+ //WHEN
+ const { body } = await mockApp
+ .post("/results")
+ .set("Authorization", `Bearer ${uid}`)
+ .send({
+ result: {
+ acc: 86,
+ afkDuration: 5,
+ bailedOut: false,
+ blindMode: false,
+ charStats: [100, 2, 3, 5],
+ chartData: { wpm: [1, 2, 3], raw: [50, 55, 56], err: [0, 2, 0] },
+ consistency: 23.5,
+ difficulty: "normal",
+ funbox: "none",
+ hash: "hash",
+ incompleteTestSeconds: 2,
+ incompleteTests: [{ acc: 75, seconds: 10 }],
+ keyConsistency: 12,
+ keyDuration: [0, 3, 5],
+ keySpacing: [0, 2, 4],
+ language: "english",
+ lazyMode: false,
+ mode: "time",
+ mode2: "15",
+ numbers: false,
+ punctuation: false,
+ rawWpm: 99,
+ restartCount: 4,
+ tags: ["tagOneId", "tagTwoId"],
+ testDuration: 15.1,
+ timestamp: 1000,
+ uid,
+ wpmConsistency: 55,
+ wpm: 80,
+ stopOnLetter: false,
+ //new required
+ charTotal: 5,
+ keyOverlap: 7,
+ lastKeyToEnd: 9,
+ startToFirstKey: 11,
+ },
+ })
+ .expect(200);
+
+ expect(body.message).toEqual("Result saved");
+ expect(body.data).toEqual({
+ isPb: true,
+ tagPbs: [],
+ xp: 0,
+ dailyXpBonus: false,
+ xpBreakdown: {},
+ streak: 0,
+ insertedId: insertedId.toHexString(),
+ });
+
+ expect(addResultMock).toHaveBeenCalledWith(
+ uid,
+ expect.objectContaining({
+ acc: 86,
+ afkDuration: 5,
+ charStats: [100, 2, 3, 5],
+ chartData: {
+ err: [0, 2, 0],
+ raw: [50, 55, 56],
+ wpm: [1, 2, 3],
+ },
+ consistency: 23.5,
+ incompleteTestSeconds: 2,
+ isPb: true,
+ keyConsistency: 12,
+ keyDurationStats: {
+ average: 2.67,
+ sd: 2.05,
+ },
+ keySpacingStats: {
+ average: 2,
+ sd: 1.63,
+ },
+ mode: "time",
+ mode2: "15",
+ name: "bob",
+ rawWpm: 99,
+ restartCount: 4,
+ tags: ["tagOneId", "tagTwoId"],
+ testDuration: 15.1,
+ uid: "123456",
+ wpm: 80,
+ })
+ );
+ });
+ it("should fail if result saving is disabled", async () => {
+ //GIVEN
+ await enableResultsSaving(false);
+
+ //WHEN
+ const { body } = await mockApp
+ .post("/results")
+ .set("Authorization", `Bearer ${uid}`)
+ .send({})
+ .expect(503);
+
+ //THEN
+ expect(body.message).toEqual("Results are not being saved at this time.");
+ });
+ it("should fail without mandatory properties", async () => {
+ //GIVEN
+
+ //WHEN
+ const { body } = await mockApp
+ .post("/results")
+ .set("Authorization", `Bearer ${uid}`)
+ .send({})
+ .expect(422);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Invalid request data schema",
+ validationErrors: ['"result" Required'],
+ });
+ });
+ it("should fail with unknown properties", async () => {
+ //GIVEN
+
+ //WHEN
+ const { body } = await mockApp
+ .post("/results")
+ .set("Authorization", `Bearer ${uid}`)
+ .send({
+ result: {
+ acc: 86,
+ afkDuration: 5,
+ bailedOut: false,
+ blindMode: false,
+ charStats: [100, 2, 3, 5],
+ chartData: { wpm: [1, 2, 3], raw: [50, 55, 56], err: [0, 2, 0] },
+ consistency: 23.5,
+ difficulty: "normal",
+ funbox: "none",
+ hash: "hash",
+ incompleteTestSeconds: 2,
+ incompleteTests: [{ acc: 75, seconds: 10 }],
+ keyConsistency: 12,
+ keyDuration: [0, 3, 5],
+ keySpacing: [0, 2, 4],
+ language: "english",
+ lazyMode: false,
+ mode: "time",
+ mode2: "15",
+ numbers: false,
+ punctuation: false,
+ rawWpm: 99,
+ restartCount: 4,
+ tags: ["tagOneId", "tagTwoId"],
+ testDuration: 15.1,
+ timestamp: 1000,
+ uid,
+ wpmConsistency: 55,
+ wpm: 80,
+ stopOnLetter: false,
+ //new required
+ charTotal: 5,
+ keyOverlap: 7,
+ lastKeyToEnd: 9,
+ startToFirstKey: 11,
+ extra2: "value",
+ },
+ extra: "value",
+ })
+ .expect(422);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Invalid request data schema",
+ validationErrors: [
+ `"result" Unrecognized key(s) in object: 'extra2'`,
+ "Unrecognized key(s) in object: 'extra'",
+ ],
+ });
+ });
+
+ it("should fail invalid properties", async () => {
+ //GIVEN
+
+ //WHEN
+ const { body } = await mockApp
+ .post("/results")
+ .set("Authorization", `Bearer ${uid}`)
+ //TODO add all properties
+ .send({ result: { acc: 25 } })
+ .expect(422);
+
+ //THEN
+ /*
+ expect(body).toEqual({
+ message: "Invalid request data schema",
+ validationErrors: [
+ ],
+ });
+ */
+ });
+ });
+});
async function enablePremiumFeatures(premium: boolean): Promise {
const mockConfig = _.merge(await configuration, {
@@ -263,3 +797,57 @@ async function enablePremiumFeatures(premium: boolean): Promise {
mockConfig
);
}
+function givenDbResult(
+ uid: string,
+ customize?: Partial
+): MonkeyTypes.DBResult {
+ return {
+ _id: new ObjectId(),
+ wpm: Math.random() * 100,
+ rawWpm: Math.random() * 100,
+ charStats: [
+ Math.round(Math.random() * 10),
+ Math.round(Math.random() * 10),
+ Math.round(Math.random() * 10),
+ Math.round(Math.random() * 10),
+ ],
+ acc: 80 + Math.random() * 20, //min accuracy is 75%
+ mode: "time",
+ mode2: "60",
+ timestamp: Math.round(Math.random() * 100),
+ testDuration: 1 + Math.random() * 100,
+ consistency: Math.random() * 100,
+ keyConsistency: Math.random() * 100,
+ uid,
+ keySpacingStats: { average: Math.random() * 100, sd: Math.random() },
+ keyDurationStats: { average: Math.random() * 100, sd: Math.random() },
+ isPb: true,
+ chartData: {
+ wpm: [Math.random() * 100],
+ raw: [Math.random() * 100],
+ err: [Math.random() * 100],
+ },
+ name: "testName",
+ ...customize,
+ };
+}
+
+async function acceptApeKeys(enabled: boolean): Promise {
+ const mockConfig = _.merge(await configuration, {
+ apeKeys: { acceptKeys: enabled },
+ });
+
+ vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
+ mockConfig
+ );
+}
+
+async function enableResultsSaving(enabled: boolean): Promise {
+ const mockConfig = _.merge(await configuration, {
+ results: { savingEnabled: enabled },
+ });
+
+ vi.spyOn(Configuration, "getCachedConfiguration").mockResolvedValue(
+ mockConfig
+ );
+}
diff --git a/backend/__tests__/dal/admin-uids.spec.ts b/backend/__tests__/dal/admin-uids.spec.ts
new file mode 100644
index 000000000000..daadbbcab537
--- /dev/null
+++ b/backend/__tests__/dal/admin-uids.spec.ts
@@ -0,0 +1,29 @@
+import { ObjectId } from "mongodb";
+import * as AdminUidsDal from "../../src/dal/admin-uids";
+
+describe("AdminUidsDal", () => {
+ describe("isAdmin", () => {
+ it("should return true for existing admin user", async () => {
+ //GIVEN
+ const uid = new ObjectId().toHexString();
+ await AdminUidsDal.getCollection().insertOne({
+ _id: new ObjectId(),
+ uid: uid,
+ });
+
+ //WHEN / THEN
+ expect(await AdminUidsDal.isAdmin(uid)).toBe(true);
+ });
+
+ it("should return false for non-existing admin user", async () => {
+ //GIVEN
+ await AdminUidsDal.getCollection().insertOne({
+ _id: new ObjectId(),
+ uid: "admin",
+ });
+
+ //WHEN / THEN
+ expect(await AdminUidsDal.isAdmin("regularUser")).toBe(false);
+ });
+ });
+});
diff --git a/backend/__tests__/dal/leaderboards.spec.ts b/backend/__tests__/dal/leaderboards.spec.ts
index 910c37794ce8..f96822a9bd3a 100644
--- a/backend/__tests__/dal/leaderboards.spec.ts
+++ b/backend/__tests__/dal/leaderboards.spec.ts
@@ -4,6 +4,8 @@ import * as UserDal from "../../src/dal/user";
import * as LeaderboardsDal from "../../src/dal/leaderboards";
import * as PublicDal from "../../src/dal/public";
import * as Configuration from "../../src/init/configuration";
+import type { DBLeaderboardEntry } from "../../src/dal/leaderboards";
+import type { PersonalBest } from "@monkeytype/contracts/schemas/shared";
const configuration = Configuration.getCachedConfiguration();
import * as DB from "../../src/init/db";
@@ -29,7 +31,9 @@ describe("LeaderboardsDal", () => {
//THEN
expect(result).toHaveLength(1);
- expect(result[0]).toHaveProperty("uid", applicableUser.uid);
+ expect(
+ (result as LeaderboardsDal.DBLeaderboardEntry[])[0]
+ ).toHaveProperty("uid", applicableUser.uid);
});
it("should create leaderboard time english 15", async () => {
@@ -46,7 +50,7 @@ describe("LeaderboardsDal", () => {
"15",
"english",
0
- )) as SharedTypes.LeaderboardEntry[];
+ )) as DBLeaderboardEntry[];
//THEN
const lb = result.map((it) => _.omit(it, ["_id"]));
@@ -72,7 +76,7 @@ describe("LeaderboardsDal", () => {
"60",
"english",
0
- )) as SharedTypes.LeaderboardEntry[];
+ )) as LeaderboardsDal.DBLeaderboardEntry[];
//THEN
const lb = result.map((it) => _.omit(it, ["_id"]));
@@ -98,7 +102,7 @@ describe("LeaderboardsDal", () => {
"60",
"english",
0
- )) as SharedTypes.LeaderboardEntry[];
+ )) as DBLeaderboardEntry[];
//THEN
expect(lb[0]).not.toHaveProperty("discordId");
@@ -121,7 +125,7 @@ describe("LeaderboardsDal", () => {
"15",
"english",
0
- )) as SharedTypes.LeaderboardEntry[];
+ )) as DBLeaderboardEntry[];
//THEN
expect(lb[0]).not.toHaveProperty("consistency");
@@ -183,7 +187,7 @@ describe("LeaderboardsDal", () => {
"15",
"english",
0
- )) as SharedTypes.LeaderboardEntry[];
+ )) as DBLeaderboardEntry[];
//THEN
const lb = result.map((it) => _.omit(it, ["_id"]));
@@ -219,7 +223,7 @@ describe("LeaderboardsDal", () => {
"15",
"english",
0
- )) as SharedTypes.LeaderboardEntry[];
+ )) as DBLeaderboardEntry[];
//THEN
const lb = result.map((it) => _.omit(it, ["_id"]));
@@ -251,7 +255,7 @@ describe("LeaderboardsDal", () => {
"15",
"english",
0
- )) as SharedTypes.LeaderboardEntry[];
+ )) as DBLeaderboardEntry[];
//THEN
expect(result[0]?.isPremium).toBeUndefined();
@@ -263,8 +267,10 @@ function expectedLbEntry(
time: string,
{ rank, user, badgeId, isPremium }: ExpectedLbEntry
) {
- const lbBest: SharedTypes.PersonalBest =
- user.lbPersonalBests?.time[time].english;
+ // @ts-expect-error
+ const lbBest: PersonalBest =
+ // @ts-expect-error
+ user.lbPersonalBests?.time[Number.parseInt(time)].english;
return {
rank,
@@ -308,10 +314,10 @@ async function createUser(
}
function lbBests(
- pb15?: SharedTypes.PersonalBest,
- pb60?: SharedTypes.PersonalBest
+ pb15?: PersonalBest,
+ pb60?: PersonalBest
): MonkeyTypes.LbPersonalBests {
- const result = { time: {} };
+ const result: MonkeyTypes.LbPersonalBests = { time: {} };
if (pb15) result.time["15"] = { english: pb15 };
if (pb60) result.time["60"] = { english: pb60 };
return result;
@@ -321,7 +327,7 @@ function pb(
wpm: number,
acc: number = 90,
timestamp: number = 1
-): SharedTypes.PersonalBest {
+): PersonalBest {
return {
acc,
consistency: 100,
@@ -335,7 +341,7 @@ function pb(
};
}
-function premium(expirationDeltaSeconds) {
+function premium(expirationDeltaSeconds: number) {
return {
premium: {
startTimestamp: 0,
diff --git a/backend/__tests__/dal/preset.spec.ts b/backend/__tests__/dal/preset.spec.ts
index f360e936de0d..6e5a10f30690 100644
--- a/backend/__tests__/dal/preset.spec.ts
+++ b/backend/__tests__/dal/preset.spec.ts
@@ -1,18 +1,23 @@
import { ObjectId } from "mongodb";
import * as PresetDal from "../../src/dal/preset";
import _ from "lodash";
-import { off } from "process";
describe("PresetDal", () => {
describe("readPreset", () => {
it("should read", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
- const first = await PresetDal.addPreset(uid, "first", { ads: "sellout" });
- const second = await PresetDal.addPreset(uid, "second", {
- ads: "result",
+ const first = await PresetDal.addPreset(uid, {
+ name: "first",
+ config: { ads: "sellout" },
});
- await PresetDal.addPreset("unknown", "unknown", {});
+ const second = await PresetDal.addPreset(uid, {
+ name: "second",
+ config: {
+ ads: "result",
+ },
+ });
+ await PresetDal.addPreset("unknown", { name: "unknown", config: {} });
//WHEN
const read = await PresetDal.getPresets(uid);
@@ -43,24 +48,27 @@ describe("PresetDal", () => {
//GIVEN
const uid = new ObjectId().toHexString();
for (let i = 0; i < 10; i++) {
- await PresetDal.addPreset(uid, "test", {} as any);
+ await PresetDal.addPreset(uid, { name: "test", config: {} });
}
//WHEN / THEN
expect(() =>
- PresetDal.addPreset(uid, "max", {} as any)
+ PresetDal.addPreset(uid, { name: "max", config: {} })
).rejects.toThrowError("Too many presets");
});
it("should add preset", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
for (let i = 0; i < 9; i++) {
- await PresetDal.addPreset(uid, "test", {} as any);
+ await PresetDal.addPreset(uid, { name: "test", config: {} });
}
//WHEN
- const newPreset = await PresetDal.addPreset(uid, "new", {
- ads: "sellout",
+ const newPreset = await PresetDal.addPreset(uid, {
+ name: "new",
+ config: {
+ ads: "sellout",
+ },
});
//THEN
@@ -82,31 +90,44 @@ describe("PresetDal", () => {
describe("editPreset", () => {
it("should not fail if preset is unknown", async () => {
- await PresetDal.editPreset(
- "uid",
- new ObjectId().toHexString(),
- "new",
- undefined
- );
+ await PresetDal.editPreset("uid", {
+ _id: new ObjectId().toHexString(),
+ name: "new",
+ config: {},
+ });
});
+
it("should edit", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
const decoyUid = new ObjectId().toHexString();
const first = (
- await PresetDal.addPreset(uid, "first", { ads: "sellout" })
+ await PresetDal.addPreset(uid, {
+ name: "first",
+ config: { ads: "sellout" },
+ })
).presetId;
const second = (
- await PresetDal.addPreset(uid, "second", {
- ads: "result",
+ await PresetDal.addPreset(uid, {
+ name: "second",
+ config: {
+ ads: "result",
+ },
})
).presetId;
const decoy = (
- await PresetDal.addPreset(decoyUid, "unknown", { ads: "result" })
+ await PresetDal.addPreset(decoyUid, {
+ name: "unknown",
+ config: { ads: "result" },
+ })
).presetId;
//WHEN
- await PresetDal.editPreset(uid, first, "newName", { ads: "off" });
+ await PresetDal.editPreset(uid, {
+ _id: first,
+ name: "newName",
+ config: { ads: "off" },
+ });
//THEN
const read = await PresetDal.getPresets(uid);
@@ -143,37 +164,18 @@ describe("PresetDal", () => {
//GIVEN
const uid = new ObjectId().toHexString();
const first = (
- await PresetDal.addPreset(uid, "first", { ads: "sellout" })
+ await PresetDal.addPreset(uid, {
+ name: "first",
+ config: { ads: "sellout" },
+ })
).presetId;
- //WHEN undefined
- await PresetDal.editPreset(uid, first, "newName", undefined);
- expect(await PresetDal.getPresets(uid)).toEqual(
- expect.arrayContaining([
- expect.objectContaining({
- _id: new ObjectId(first),
- uid: uid,
- name: "newName",
- config: { ads: "sellout" },
- }),
- ])
- );
-
- //WHEN null
- await PresetDal.editPreset(uid, first, "newName", null);
- expect(await PresetDal.getPresets(uid)).toEqual(
- expect.arrayContaining([
- expect.objectContaining({
- _id: new ObjectId(first),
- uid: uid,
- name: "newName",
- config: { ads: "sellout" },
- }),
- ])
- );
-
//WHEN empty
- await PresetDal.editPreset(uid, first, "newName", {});
+ await PresetDal.editPreset(uid, {
+ _id: first,
+ name: "newName",
+ config: {},
+ });
expect(await PresetDal.getPresets(uid)).toEqual(
expect.arrayContaining([
expect.objectContaining({
@@ -190,11 +192,18 @@ describe("PresetDal", () => {
const uid = new ObjectId().toHexString();
const decoyUid = new ObjectId().toHexString();
const first = (
- await PresetDal.addPreset(uid, "first", { ads: "sellout" })
+ await PresetDal.addPreset(uid, {
+ name: "first",
+ config: { ads: "sellout" },
+ })
).presetId;
//WHEN
- await PresetDal.editPreset(decoyUid, first, "newName", { ads: "off" });
+ await PresetDal.editPreset(decoyUid, {
+ _id: first,
+ name: "newName",
+ config: { ads: "off" },
+ });
//THEN
const read = await PresetDal.getPresets(uid);
@@ -222,12 +231,20 @@ describe("PresetDal", () => {
//GIVEN
const uid = new ObjectId().toHexString();
const decoyUid = new ObjectId().toHexString();
- const first = (await PresetDal.addPreset(uid, "first", {})).presetId;
+ const first = (
+ await PresetDal.addPreset(uid, { name: "first", config: {} })
+ ).presetId;
const second = (
- await PresetDal.addPreset(uid, "second", { ads: "result" })
+ await PresetDal.addPreset(uid, {
+ name: "second",
+ config: { ads: "result" },
+ })
).presetId;
const decoy = (
- await PresetDal.addPreset(decoyUid, "unknown", { ads: "result" })
+ await PresetDal.addPreset(decoyUid, {
+ name: "unknown",
+ config: { ads: "result" },
+ })
).presetId;
//WHEN
@@ -262,7 +279,10 @@ describe("PresetDal", () => {
const uid = new ObjectId().toHexString();
const decoyUid = new ObjectId().toHexString();
const first = (
- await PresetDal.addPreset(uid, "first", { ads: "sellout" })
+ await PresetDal.addPreset(uid, {
+ name: "first",
+ config: { ads: "sellout" },
+ })
).presetId;
//WHEN
@@ -294,10 +314,16 @@ describe("PresetDal", () => {
//GIVEN
const uid = new ObjectId().toHexString();
const decoyUid = new ObjectId().toHexString();
- await PresetDal.addPreset(uid, "first", {});
- await PresetDal.addPreset(uid, "second", { ads: "result" });
+ await PresetDal.addPreset(uid, { name: "first", config: {} });
+ await PresetDal.addPreset(uid, {
+ name: "second",
+ config: { ads: "result" },
+ });
const decoy = (
- await PresetDal.addPreset(decoyUid, "unknown", { ads: "result" })
+ await PresetDal.addPreset(decoyUid, {
+ name: "unknown",
+ config: { ads: "result" },
+ })
).presetId;
//WHEN
diff --git a/backend/__tests__/global-setup.ts b/backend/__tests__/global-setup.ts
index 461ccb2f42bd..64c96042aa24 100644
--- a/backend/__tests__/global-setup.ts
+++ b/backend/__tests__/global-setup.ts
@@ -1,14 +1,17 @@
import * as MongoDbMock from "vitest-mongodb";
-export async function setup({ provide }): Promise {
- await MongoDbMock.setup({
- serverOptions: {
- binary: {
- version: "6.0.12",
- },
- },
- });
+export async function setup(): Promise {
+ process.env.TZ = "UTC";
+ await MongoDbMock.setup(MongoDbMockConfig);
}
export async function teardown(): Promise {
await MongoDbMock.teardown();
}
+
+export const MongoDbMockConfig = {
+ serverOptions: {
+ binary: {
+ version: "6.0.12",
+ },
+ },
+};
diff --git a/backend/__tests__/middlewares/auth.spec.ts b/backend/__tests__/middlewares/auth.spec.ts
index f4e672249437..49af130f4557 100644
--- a/backend/__tests__/middlewares/auth.spec.ts
+++ b/backend/__tests__/middlewares/auth.spec.ts
@@ -1,6 +1,6 @@
import * as AuthUtils from "../../src/utils/auth";
import * as Auth from "../../src/middlewares/auth";
-import { DecodedIdToken } from "firebase-admin/lib/auth/token-verifier";
+import { DecodedIdToken } from "firebase-admin/auth";
import { NextFunction, Request, Response } from "express";
import { getCachedConfiguration } from "../../src/init/configuration";
import * as ApeKeys from "../../src/dal/ape-keys";
@@ -8,6 +8,11 @@ import { ObjectId } from "mongodb";
import { hashSync } from "bcrypt";
import MonkeyError from "../../src/utils/error";
import * as Misc from "../../src/utils/misc";
+import {
+ EndpointMetadata,
+ RequestAuthenticationOptions,
+} from "@monkeytype/contracts/schemas/api";
+import * as Prometheus from "../../src/utils/prometheus";
const mockDecodedToken: DecodedIdToken = {
uid: "123456789",
@@ -31,12 +36,11 @@ const mockApeKey = {
vi.spyOn(ApeKeys, "getApeKey").mockResolvedValue(mockApeKey);
vi.spyOn(ApeKeys, "updateLastUsedOn").mockResolvedValue();
const isDevModeMock = vi.spyOn(Misc, "isDevEnvironment");
+let mockRequest: Partial;
+let mockResponse: Partial;
+let nextFunction: NextFunction;
describe("middlewares/auth", () => {
- let mockRequest: Partial;
- let mockResponse: Partial;
- let nextFunction: NextFunction;
-
beforeEach(async () => {
isDevModeMock.mockReturnValue(true);
let config = await getCachedConfiguration(true);
@@ -82,22 +86,15 @@ describe("middlewares/auth", () => {
requireFreshToken: true,
});
- let result;
-
- try {
- result = await authenticateRequest(
+ expect(() =>
+ authenticateRequest(
mockRequest as Request,
mockResponse as Response,
nextFunction
- );
- } catch (e) {
- result = e;
- }
-
- expect(result.message).toBe(
+ )
+ ).rejects.toThrowError(
"Unauthorized\nStack: This endpoint requires a fresh token"
);
- expect(nextFunction).toHaveBeenCalledTimes(1);
});
it("should allow the request if token is fresh", async () => {
Date.now = vi.fn(() => 10000);
@@ -258,4 +255,375 @@ describe("middlewares/auth", () => {
);
});
});
+
+ describe("authenticateTsRestRequest", () => {
+ const prometheusRecordAuthTimeMock = vi.spyOn(Prometheus, "recordAuthTime");
+ const prometheusIncrementAuthMock = vi.spyOn(Prometheus, "incrementAuth");
+
+ beforeEach(() =>
+ [prometheusIncrementAuthMock, prometheusRecordAuthTimeMock].forEach(
+ (it) => it.mockReset()
+ )
+ );
+
+ it("should fail if token is not fresh", async () => {
+ //GIVEN
+ Date.now = vi.fn(() => 60001);
+
+ //WHEN
+ expect(() =>
+ authenticate({}, { requireFreshToken: true })
+ ).rejects.toThrowError(
+ "Unauthorized\nStack: This endpoint requires a fresh token"
+ );
+
+ //THEN
+
+ expect(nextFunction).not.toHaveBeenCalled();
+ expect(prometheusIncrementAuthMock).not.toHaveBeenCalled();
+ expect(prometheusRecordAuthTimeMock).not.toHaveBeenCalled();
+ });
+ it("should allow the request if token is fresh", async () => {
+ //GIVEN
+ Date.now = vi.fn(() => 10000);
+
+ //WHEN
+ const result = await authenticate({}, { requireFreshToken: true });
+
+ //THEN
+ const decodedToken = result.decodedToken;
+ expect(decodedToken?.type).toBe("Bearer");
+ expect(decodedToken?.email).toBe(mockDecodedToken.email);
+ expect(decodedToken?.uid).toBe(mockDecodedToken.uid);
+ expect(nextFunction).toHaveBeenCalledOnce();
+
+ expect(prometheusIncrementAuthMock).toHaveBeenCalledWith("Bearer");
+ expect(prometheusRecordAuthTimeMock).toHaveBeenCalledOnce();
+ });
+ it("should allow the request if apeKey is supported", async () => {
+ //WHEN
+ const result = await authenticate(
+ { headers: { authorization: "ApeKey aWQua2V5" } },
+ { acceptApeKeys: true }
+ );
+
+ //THEN
+ const decodedToken = result.decodedToken;
+ expect(decodedToken?.type).toBe("ApeKey");
+ expect(decodedToken?.email).toBe("");
+ expect(decodedToken?.uid).toBe("123");
+ expect(nextFunction).toHaveBeenCalledTimes(1);
+ });
+ it("should fail with apeKey if apeKey is not supported", async () => {
+ //WHEN
+ await expect(() =>
+ authenticate(
+ { headers: { authorization: "ApeKey aWQua2V5" } },
+ { acceptApeKeys: false }
+ )
+ ).rejects.toThrowError("This endpoint does not accept ApeKeys");
+
+ //THEN
+ });
+ it("should fail with apeKey if apeKeys are disabled", async () => {
+ //GIVEN
+
+ //@ts-expect-error
+ mockRequest.ctx.configuration.apeKeys.acceptKeys = false;
+
+ //WHEN
+ await expect(() =>
+ authenticate(
+ { headers: { authorization: "ApeKey aWQua2V5" } },
+ { acceptApeKeys: false }
+ )
+ ).rejects.toThrowError("ApeKeys are not being accepted at this time");
+
+ //THEN
+ });
+ it("should allow the request with authentation on public endpoint", async () => {
+ //WHEN
+ const result = await authenticate({}, { isPublic: true });
+
+ //THEN
+ const decodedToken = result.decodedToken;
+ expect(decodedToken?.type).toBe("Bearer");
+ expect(decodedToken?.email).toBe(mockDecodedToken.email);
+ expect(decodedToken?.uid).toBe(mockDecodedToken.uid);
+ expect(nextFunction).toHaveBeenCalledTimes(1);
+ });
+ it("should allow the request without authentication on public endpoint", async () => {
+ //WHEN
+ const result = await authenticate({ headers: {} }, { isPublic: true });
+
+ //THEN
+ const decodedToken = result.decodedToken;
+ expect(decodedToken?.type).toBe("None");
+ expect(decodedToken?.email).toBe("");
+ expect(decodedToken?.uid).toBe("");
+ expect(nextFunction).toHaveBeenCalledTimes(1);
+
+ expect(prometheusIncrementAuthMock).toHaveBeenCalledWith("None");
+ expect(prometheusRecordAuthTimeMock).toHaveBeenCalledOnce();
+ });
+ it("should allow the request with apeKey on public endpoint", async () => {
+ //WHEN
+ const result = await authenticate(
+ { headers: { authorization: "ApeKey aWQua2V5" } },
+ { isPublic: true }
+ );
+
+ //THEN
+ const decodedToken = result.decodedToken;
+ expect(decodedToken?.type).toBe("ApeKey");
+ expect(decodedToken?.email).toBe("");
+ expect(decodedToken?.uid).toBe("123");
+ expect(nextFunction).toHaveBeenCalledTimes(1);
+
+ expect(prometheusIncrementAuthMock).toHaveBeenCalledWith("ApeKey");
+ expect(prometheusRecordAuthTimeMock).toHaveBeenCalledOnce();
+ });
+ it("should allow request with Uid on dev", async () => {
+ //WHEN
+ const result = await authenticate({
+ headers: { authorization: "Uid 123" },
+ });
+
+ //THEN
+ const decodedToken = result.decodedToken;
+ expect(decodedToken?.type).toBe("Bearer");
+ expect(decodedToken?.email).toBe("");
+ expect(decodedToken?.uid).toBe("123");
+ expect(nextFunction).toHaveBeenCalledTimes(1);
+ });
+ it("should allow request with Uid and email on dev", async () => {
+ const result = await authenticate({
+ headers: { authorization: "Uid 123|test@example.com" },
+ });
+
+ //THEN
+ const decodedToken = result.decodedToken;
+ expect(decodedToken?.type).toBe("Bearer");
+ expect(decodedToken?.email).toBe("test@example.com");
+ expect(decodedToken?.uid).toBe("123");
+ expect(nextFunction).toHaveBeenCalledTimes(1);
+ });
+ it("should fail request with Uid on non-dev", async () => {
+ //GIVEN
+ isDevModeMock.mockReturnValue(false);
+
+ //WHEN / THEN
+ await expect(() =>
+ authenticate({ headers: { authorization: "Uid 123" } })
+ ).rejects.toThrow(
+ new MonkeyError(401, "Baerer type uid is not supported")
+ );
+ });
+ it("should fail without authentication", async () => {
+ await expect(() => authenticate({ headers: {} })).rejects.toThrowError(
+ "Unauthorized\nStack: endpoint: /api/v1 no authorization header found"
+ );
+
+ //THEH
+ expect(prometheusIncrementAuthMock).not.toHaveBeenCalled();
+ expect(prometheusRecordAuthTimeMock).toHaveBeenCalledWith(
+ "None",
+ "failure",
+ expect.anything(),
+ expect.anything()
+ );
+ });
+ it("should fail with empty authentication", async () => {
+ await expect(() =>
+ authenticate({ headers: { authorization: "" } })
+ ).rejects.toThrowError(
+ "Unauthorized\nStack: endpoint: /api/v1 no authorization header found"
+ );
+
+ //THEH
+ expect(prometheusIncrementAuthMock).not.toHaveBeenCalled();
+ expect(prometheusRecordAuthTimeMock).toHaveBeenCalledWith(
+ "",
+ "failure",
+ expect.anything(),
+ expect.anything()
+ );
+ });
+ it("should fail with missing authentication token", async () => {
+ await expect(() =>
+ authenticate({ headers: { authorization: "Bearer" } })
+ ).rejects.toThrowError(
+ "Missing authentication token\nStack: authenticateWithAuthHeader"
+ );
+
+ //THEH
+ expect(prometheusIncrementAuthMock).not.toHaveBeenCalled();
+ expect(prometheusRecordAuthTimeMock).toHaveBeenCalledWith(
+ "Bearer",
+ "failure",
+ expect.anything(),
+ expect.anything()
+ );
+ });
+ it("should fail with unknown authentication scheme", async () => {
+ await expect(() =>
+ authenticate({ headers: { authorization: "unknown format" } })
+ ).rejects.toThrowError(
+ 'Unknown authentication scheme\nStack: The authentication scheme "unknown" is not implemented'
+ );
+
+ //THEH
+ expect(prometheusIncrementAuthMock).not.toHaveBeenCalled();
+ expect(prometheusRecordAuthTimeMock).toHaveBeenCalledWith(
+ "unknown",
+ "failure",
+ expect.anything(),
+ expect.anything()
+ );
+ });
+ it("should record country if provided", async () => {
+ const prometheusRecordRequestCountryMock = vi.spyOn(
+ Prometheus,
+ "recordRequestCountry"
+ );
+
+ await authenticate(
+ { headers: { "cf-ipcountry": "gb" } },
+ { isPublic: true }
+ );
+
+ //THEN
+ expect(prometheusRecordRequestCountryMock).toHaveBeenCalledWith(
+ "gb",
+ expect.anything()
+ );
+ });
+ it("should allow the request with authentation on dev public endpoint", async () => {
+ //WHEN
+ const result = await authenticate({}, { isPublicOnDev: true });
+
+ //THEN
+ const decodedToken = result.decodedToken;
+ expect(decodedToken?.type).toBe("Bearer");
+ expect(decodedToken?.email).toBe(mockDecodedToken.email);
+ expect(decodedToken?.uid).toBe(mockDecodedToken.uid);
+ expect(nextFunction).toHaveBeenCalledTimes(1);
+ });
+ it("should allow the request without authentication on dev public endpoint", async () => {
+ //WHEN
+ const result = await authenticate(
+ { headers: {} },
+ { isPublicOnDev: true }
+ );
+
+ //THEN
+ const decodedToken = result.decodedToken;
+ expect(decodedToken?.type).toBe("None");
+ expect(decodedToken?.email).toBe("");
+ expect(decodedToken?.uid).toBe("");
+ expect(nextFunction).toHaveBeenCalledTimes(1);
+
+ expect(prometheusIncrementAuthMock).toHaveBeenCalledWith("None");
+ expect(prometheusRecordAuthTimeMock).toHaveBeenCalledOnce();
+ });
+ it("should allow the request with apeKey on dev public endpoint", async () => {
+ //WHEN
+ const result = await authenticate(
+ { headers: { authorization: "ApeKey aWQua2V5" } },
+ { acceptApeKeys: true, isPublicOnDev: true }
+ );
+
+ //THEN
+ const decodedToken = result.decodedToken;
+ expect(decodedToken?.type).toBe("ApeKey");
+ expect(decodedToken?.email).toBe("");
+ expect(decodedToken?.uid).toBe("123");
+ expect(nextFunction).toHaveBeenCalledTimes(1);
+
+ expect(prometheusIncrementAuthMock).toHaveBeenCalledWith("ApeKey");
+ expect(prometheusRecordAuthTimeMock).toHaveBeenCalledOnce();
+ });
+ it("should allow with apeKey if apeKeys are disabled on dev public endpoint", async () => {
+ //GIVEN
+
+ //@ts-expect-error
+ mockRequest.ctx.configuration.apeKeys.acceptKeys = false;
+
+ //WHEN
+ const result = await authenticate(
+ { headers: { authorization: "ApeKey aWQua2V5" } },
+ { acceptApeKeys: true, isPublicOnDev: true }
+ );
+
+ //THEN
+ const decodedToken = result.decodedToken;
+ expect(decodedToken?.type).toBe("ApeKey");
+ expect(decodedToken?.email).toBe("");
+ expect(decodedToken?.uid).toBe("123");
+ expect(nextFunction).toHaveBeenCalledTimes(1);
+
+ expect(prometheusIncrementAuthMock).toHaveBeenCalledWith("ApeKey");
+ expect(prometheusRecordAuthTimeMock).toHaveBeenCalledOnce();
+ });
+ it("should allow the request with authentation on dev public endpoint in production", async () => {
+ //WHEN
+ isDevModeMock.mockReturnValue(false);
+ const result = await authenticate({}, { isPublicOnDev: true });
+
+ //THEN
+ const decodedToken = result.decodedToken;
+ expect(decodedToken?.type).toBe("Bearer");
+ expect(decodedToken?.email).toBe(mockDecodedToken.email);
+ expect(decodedToken?.uid).toBe(mockDecodedToken.uid);
+ expect(nextFunction).toHaveBeenCalledTimes(1);
+ });
+ it("should fail without authentication on dev public endpoint in production", async () => {
+ //WHEN
+ isDevModeMock.mockReturnValue(false);
+
+ //THEN
+ await expect(() =>
+ authenticate({ headers: {} }, { isPublicOnDev: true })
+ ).rejects.toThrowError("Unauthorized");
+ });
+ it("should allow with apeKey on dev public endpoint in production", async () => {
+ //WHEN
+ isDevModeMock.mockReturnValue(false);
+ const result = await authenticate(
+ { headers: { authorization: "ApeKey aWQua2V5" } },
+ { acceptApeKeys: true, isPublicOnDev: true }
+ );
+
+ //THEN
+ const decodedToken = result.decodedToken;
+ expect(decodedToken?.type).toBe("ApeKey");
+ expect(decodedToken?.email).toBe("");
+ expect(decodedToken?.uid).toBe("123");
+ expect(nextFunction).toHaveBeenCalledTimes(1);
+
+ expect(prometheusIncrementAuthMock).toHaveBeenCalledWith("ApeKey");
+ expect(prometheusRecordAuthTimeMock).toHaveBeenCalledOnce();
+ });
+ });
});
+
+async function authenticate(
+ request: Partial,
+ authenticationOptions?: RequestAuthenticationOptions
+): Promise<{ decodedToken: MonkeyTypes.DecodedToken }> {
+ const mergedRequest = {
+ ...mockRequest,
+ ...request,
+ tsRestRoute: {
+ metadata: { authenticationOptions } as EndpointMetadata,
+ },
+ } as any;
+
+ await Auth.authenticateTsRestRequest()(
+ mergedRequest,
+ mockResponse as Response,
+ nextFunction
+ );
+
+ return { decodedToken: mergedRequest.ctx.decodedToken };
+}
diff --git a/backend/__tests__/setup-tests.ts b/backend/__tests__/setup-tests.ts
index 5d267700c3f4..0a1f16757848 100644
--- a/backend/__tests__/setup-tests.ts
+++ b/backend/__tests__/setup-tests.ts
@@ -1,6 +1,7 @@
import { Collection, Db, MongoClient, WithId } from "mongodb";
import { afterAll, beforeAll, afterEach } from "vitest";
import * as MongoDbMock from "vitest-mongodb";
+import { MongoDbMockConfig } from "./global-setup";
process.env["MODE"] = "dev";
//process.env["MONGOMS_DISTRO"] = "ubuntu-22.04";
@@ -15,9 +16,8 @@ let client: MongoClient;
const collectionsForCleanUp = ["users"];
beforeAll(async () => {
- await MongoDbMock.setup({
- //don't add any configuration here, add to global-setup.ts instead.
- });
+ //don't add any configuration here, add to global-setup.ts instead.
+ await MongoDbMock.setup(MongoDbMockConfig);
client = new MongoClient(globalThis.__MONGO_URI__);
await client.connect();
diff --git a/backend/__tests__/tsconfig.json b/backend/__tests__/tsconfig.json
index 0e08eb150336..ddfdc95f8843 100644
--- a/backend/__tests__/tsconfig.json
+++ b/backend/__tests__/tsconfig.json
@@ -1,18 +1,6 @@
{
+ "extends": "@monkeytype/typescript-config/base.json",
"compilerOptions": {
- "incremental": true,
- "module": "commonjs",
- "target": "es6",
- "sourceMap": false,
- "allowJs": true,
- "checkJs": true,
- "outDir": "build",
- "moduleResolution": "node",
- "resolveJsonModule": true,
- "allowSyntheticDefaultImports": true,
- "esModuleInterop": true,
- "strictNullChecks": true,
- "skipLibCheck": true,
"noEmit": true,
"types": ["vitest/globals"]
},
diff --git a/backend/__tests__/utils/misc.spec.ts b/backend/__tests__/utils/misc.spec.ts
index f8c2b0065ed4..a379e0fb07f8 100644
--- a/backend/__tests__/utils/misc.spec.ts
+++ b/backend/__tests__/utils/misc.spec.ts
@@ -1,5 +1,6 @@
import _ from "lodash";
import * as misc from "../../src/utils/misc";
+import { ObjectId } from "mongodb";
describe("Misc Utils", () => {
afterAll(() => {
@@ -605,4 +606,51 @@ describe("Misc Utils", () => {
expect(misc.formatSeconds(seconds)).toBe(expected);
});
});
+
+ describe("replaceObjectId", () => {
+ it("replaces objecId with string", () => {
+ const fromDatabase = {
+ _id: new ObjectId(),
+ test: "test",
+ number: 1,
+ };
+ expect(misc.replaceObjectId(fromDatabase)).toStrictEqual({
+ _id: fromDatabase._id.toHexString(),
+ test: "test",
+ number: 1,
+ });
+ });
+ });
+
+ describe("replaceObjectIds", () => {
+ it("replaces objecIds with string", () => {
+ const fromDatabase = {
+ _id: new ObjectId(),
+ test: "test",
+ number: 1,
+ };
+ const fromDatabase2 = {
+ _id: new ObjectId(),
+ test: "bob",
+ number: 2,
+ };
+ expect(
+ misc.replaceObjectIds([fromDatabase, fromDatabase2])
+ ).toStrictEqual([
+ {
+ _id: fromDatabase._id.toHexString(),
+ test: "test",
+ number: 1,
+ },
+ {
+ _id: fromDatabase2._id.toHexString(),
+ test: "bob",
+ number: 2,
+ },
+ ]);
+ });
+ it("handles undefined", () => {
+ expect(misc.replaceObjectIds(undefined as any)).toBeUndefined();
+ });
+ });
});
diff --git a/backend/docker/compose.yml b/backend/docker/compose.yml
index 321bcdaa7ac7..ce193c90ed11 100644
--- a/backend/docker/compose.yml
+++ b/backend/docker/compose.yml
@@ -21,7 +21,8 @@ services:
api-server:
container_name: monkeytype-api-server
- image: node:18.19.1
+ image: node:20.16.0
+ user: "node" ##this works as long as your local user has uid=1000
restart: on-failure
depends_on:
- redis
@@ -32,11 +33,13 @@ services:
ports:
- "${DOCKER_SERVER_PORT:-5005}:5005"
volumes:
- - be-modules:/monkeytype/backend/node_modules
- ../../:/monkeytype
- entrypoint: 'bash -c "cd /monkeytype/backend && npm install && npm run dev"'
+ entrypoint: 'bash -c "echo starting, this may take a while... \
+ && cd /monkeytype \
+ && npm i -g pnpm \
+ && pnpm i \
+ && npm run dev-be"'
volumes:
mongo-data:
redis-data:
- be-modules:
diff --git a/backend/package-lock.json b/backend/package-lock.json
deleted file mode 100644
index 45b9c3e81072..000000000000
--- a/backend/package-lock.json
+++ /dev/null
@@ -1,10071 +0,0 @@
-{
- "name": "monkeytype-backend",
- "version": "1.14.3",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "monkeytype-backend",
- "version": "1.14.3",
- "license": "GPL-3.0",
- "dependencies": {
- "@date-fns/utc": "1.2.0",
- "bcrypt": "5.1.1",
- "bullmq": "1.91.1",
- "chalk": "4.1.2",
- "cors": "2.8.5",
- "cron": "2.3.0",
- "date-fns": "3.6.0",
- "dotenv": "10.0.0",
- "express": "4.17.3",
- "express-rate-limit": "6.2.1",
- "firebase-admin": "12.0.0",
- "helmet": "4.6.0",
- "ioredis": "4.28.5",
- "joi": "17.6.0",
- "lodash": "4.17.21",
- "lru-cache": "7.10.1",
- "mjml": "4.15.0",
- "mongodb": "6.3.0",
- "mustache": "4.2.0",
- "node-fetch": "2.6.7",
- "nodemailer": "6.9.9",
- "nodemon": "3.0.1",
- "object-hash": "3.0.0",
- "path": "0.12.7",
- "prom-client": "14.0.1",
- "rate-limiter-flexible": "2.3.7",
- "simple-git": "3.16.0",
- "string-similarity": "4.0.4",
- "swagger-stats": "0.99.5",
- "swagger-ui-express": "4.3.0",
- "ua-parser-js": "0.7.28",
- "uuid": "9.0.1",
- "winston": "3.6.0"
- },
- "devDependencies": {
- "@types/bcrypt": "5.0.0",
- "@types/cors": "2.8.12",
- "@types/cron": "1.7.3",
- "@types/express": "4.17.21",
- "@types/ioredis": "4.28.10",
- "@types/lodash": "4.14.178",
- "@types/mustache": "4.2.2",
- "@types/node": "18.19.1",
- "@types/node-fetch": "2.6.1",
- "@types/nodemailer": "6.4.7",
- "@types/object-hash": "2.2.1",
- "@types/readline-sync": "1.4.8",
- "@types/string-similarity": "4.0.0",
- "@types/supertest": "2.0.12",
- "@types/swagger-stats": "0.95.4",
- "@types/swagger-ui-express": "4.1.3",
- "@types/ua-parser-js": "0.7.36",
- "@types/uuid": "8.3.4",
- "@vitest/coverage-v8": "1.6.0",
- "ioredis-mock": "7.4.0",
- "readline-sync": "1.4.10",
- "supertest": "6.2.3",
- "ts-node-dev": "2.0.0",
- "typescript": "5.3.3",
- "vitest": "1.6.0",
- "vitest-mongodb": "0.0.5"
- },
- "engines": {
- "node": "18.19.1",
- "npm": "10.2.4"
- }
- },
- "node_modules/@ampproject/remapping": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
- "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
- "dev": true,
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@ampproject/remapping/node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
- "dev": true,
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@aws-crypto/crc32": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz",
- "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-crypto/util": "^3.0.0",
- "@aws-sdk/types": "^3.222.0",
- "tslib": "^1.11.1"
- }
- },
- "node_modules/@aws-crypto/crc32/node_modules/tslib": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
- "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
- "optional": true,
- "peer": true
- },
- "node_modules/@aws-crypto/ie11-detection": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz",
- "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "tslib": "^1.11.1"
- }
- },
- "node_modules/@aws-crypto/ie11-detection/node_modules/tslib": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
- "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
- "optional": true,
- "peer": true
- },
- "node_modules/@aws-crypto/sha256-browser": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz",
- "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-crypto/ie11-detection": "^3.0.0",
- "@aws-crypto/sha256-js": "^3.0.0",
- "@aws-crypto/supports-web-crypto": "^3.0.0",
- "@aws-crypto/util": "^3.0.0",
- "@aws-sdk/types": "^3.222.0",
- "@aws-sdk/util-locate-window": "^3.0.0",
- "@aws-sdk/util-utf8-browser": "^3.0.0",
- "tslib": "^1.11.1"
- }
- },
- "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
- "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
- "optional": true,
- "peer": true
- },
- "node_modules/@aws-crypto/sha256-js": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz",
- "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-crypto/util": "^3.0.0",
- "@aws-sdk/types": "^3.222.0",
- "tslib": "^1.11.1"
- }
- },
- "node_modules/@aws-crypto/sha256-js/node_modules/tslib": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
- "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
- "optional": true,
- "peer": true
- },
- "node_modules/@aws-crypto/supports-web-crypto": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz",
- "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "tslib": "^1.11.1"
- }
- },
- "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
- "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
- "optional": true,
- "peer": true
- },
- "node_modules/@aws-crypto/util": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz",
- "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-sdk/types": "^3.222.0",
- "@aws-sdk/util-utf8-browser": "^3.0.0",
- "tslib": "^1.11.1"
- }
- },
- "node_modules/@aws-crypto/util/node_modules/tslib": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
- "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
- "optional": true,
- "peer": true
- },
- "node_modules/@aws-sdk/client-cognito-identity": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.451.0.tgz",
- "integrity": "sha512-xoImUiGoaXJZpOCgbWcdrU4vHJ8HG5KluaCkc32kuFobM277sjQimaUIHOGHL24M5vyo4QxcJD9CT/IhX63Vlg==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-crypto/sha256-browser": "3.0.0",
- "@aws-crypto/sha256-js": "3.0.0",
- "@aws-sdk/client-sts": "3.451.0",
- "@aws-sdk/core": "3.451.0",
- "@aws-sdk/credential-provider-node": "3.451.0",
- "@aws-sdk/middleware-host-header": "3.451.0",
- "@aws-sdk/middleware-logger": "3.451.0",
- "@aws-sdk/middleware-recursion-detection": "3.451.0",
- "@aws-sdk/middleware-signing": "3.451.0",
- "@aws-sdk/middleware-user-agent": "3.451.0",
- "@aws-sdk/region-config-resolver": "3.451.0",
- "@aws-sdk/types": "3.451.0",
- "@aws-sdk/util-endpoints": "3.451.0",
- "@aws-sdk/util-user-agent-browser": "3.451.0",
- "@aws-sdk/util-user-agent-node": "3.451.0",
- "@smithy/config-resolver": "^2.0.18",
- "@smithy/fetch-http-handler": "^2.2.6",
- "@smithy/hash-node": "^2.0.15",
- "@smithy/invalid-dependency": "^2.0.13",
- "@smithy/middleware-content-length": "^2.0.15",
- "@smithy/middleware-endpoint": "^2.2.0",
- "@smithy/middleware-retry": "^2.0.20",
- "@smithy/middleware-serde": "^2.0.13",
- "@smithy/middleware-stack": "^2.0.7",
- "@smithy/node-config-provider": "^2.1.5",
- "@smithy/node-http-handler": "^2.1.9",
- "@smithy/protocol-http": "^3.0.9",
- "@smithy/smithy-client": "^2.1.15",
- "@smithy/types": "^2.5.0",
- "@smithy/url-parser": "^2.0.13",
- "@smithy/util-base64": "^2.0.1",
- "@smithy/util-body-length-browser": "^2.0.0",
- "@smithy/util-body-length-node": "^2.1.0",
- "@smithy/util-defaults-mode-browser": "^2.0.19",
- "@smithy/util-defaults-mode-node": "^2.0.25",
- "@smithy/util-endpoints": "^1.0.4",
- "@smithy/util-retry": "^2.0.6",
- "@smithy/util-utf8": "^2.0.2",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/client-sso": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.451.0.tgz",
- "integrity": "sha512-KkYSke3Pdv3MfVH/5fT528+MKjMyPKlcLcd4zQb0x6/7Bl7EHrPh1JZYjzPLHelb+UY5X0qN8+cb8iSu1eiwIQ==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-crypto/sha256-browser": "3.0.0",
- "@aws-crypto/sha256-js": "3.0.0",
- "@aws-sdk/core": "3.451.0",
- "@aws-sdk/middleware-host-header": "3.451.0",
- "@aws-sdk/middleware-logger": "3.451.0",
- "@aws-sdk/middleware-recursion-detection": "3.451.0",
- "@aws-sdk/middleware-user-agent": "3.451.0",
- "@aws-sdk/region-config-resolver": "3.451.0",
- "@aws-sdk/types": "3.451.0",
- "@aws-sdk/util-endpoints": "3.451.0",
- "@aws-sdk/util-user-agent-browser": "3.451.0",
- "@aws-sdk/util-user-agent-node": "3.451.0",
- "@smithy/config-resolver": "^2.0.18",
- "@smithy/fetch-http-handler": "^2.2.6",
- "@smithy/hash-node": "^2.0.15",
- "@smithy/invalid-dependency": "^2.0.13",
- "@smithy/middleware-content-length": "^2.0.15",
- "@smithy/middleware-endpoint": "^2.2.0",
- "@smithy/middleware-retry": "^2.0.20",
- "@smithy/middleware-serde": "^2.0.13",
- "@smithy/middleware-stack": "^2.0.7",
- "@smithy/node-config-provider": "^2.1.5",
- "@smithy/node-http-handler": "^2.1.9",
- "@smithy/protocol-http": "^3.0.9",
- "@smithy/smithy-client": "^2.1.15",
- "@smithy/types": "^2.5.0",
- "@smithy/url-parser": "^2.0.13",
- "@smithy/util-base64": "^2.0.1",
- "@smithy/util-body-length-browser": "^2.0.0",
- "@smithy/util-body-length-node": "^2.1.0",
- "@smithy/util-defaults-mode-browser": "^2.0.19",
- "@smithy/util-defaults-mode-node": "^2.0.25",
- "@smithy/util-endpoints": "^1.0.4",
- "@smithy/util-retry": "^2.0.6",
- "@smithy/util-utf8": "^2.0.2",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/client-sts": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.451.0.tgz",
- "integrity": "sha512-48NcIRxWBdP1fom6RSjwn2R2u7SE7eeV3p+c4s7ukEOfrHhBxJfn3EpqBVQMGzdiU55qFImy+Fe81iA2lXq3Jw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-crypto/sha256-browser": "3.0.0",
- "@aws-crypto/sha256-js": "3.0.0",
- "@aws-sdk/core": "3.451.0",
- "@aws-sdk/credential-provider-node": "3.451.0",
- "@aws-sdk/middleware-host-header": "3.451.0",
- "@aws-sdk/middleware-logger": "3.451.0",
- "@aws-sdk/middleware-recursion-detection": "3.451.0",
- "@aws-sdk/middleware-sdk-sts": "3.451.0",
- "@aws-sdk/middleware-signing": "3.451.0",
- "@aws-sdk/middleware-user-agent": "3.451.0",
- "@aws-sdk/region-config-resolver": "3.451.0",
- "@aws-sdk/types": "3.451.0",
- "@aws-sdk/util-endpoints": "3.451.0",
- "@aws-sdk/util-user-agent-browser": "3.451.0",
- "@aws-sdk/util-user-agent-node": "3.451.0",
- "@smithy/config-resolver": "^2.0.18",
- "@smithy/fetch-http-handler": "^2.2.6",
- "@smithy/hash-node": "^2.0.15",
- "@smithy/invalid-dependency": "^2.0.13",
- "@smithy/middleware-content-length": "^2.0.15",
- "@smithy/middleware-endpoint": "^2.2.0",
- "@smithy/middleware-retry": "^2.0.20",
- "@smithy/middleware-serde": "^2.0.13",
- "@smithy/middleware-stack": "^2.0.7",
- "@smithy/node-config-provider": "^2.1.5",
- "@smithy/node-http-handler": "^2.1.9",
- "@smithy/protocol-http": "^3.0.9",
- "@smithy/smithy-client": "^2.1.15",
- "@smithy/types": "^2.5.0",
- "@smithy/url-parser": "^2.0.13",
- "@smithy/util-base64": "^2.0.1",
- "@smithy/util-body-length-browser": "^2.0.0",
- "@smithy/util-body-length-node": "^2.1.0",
- "@smithy/util-defaults-mode-browser": "^2.0.19",
- "@smithy/util-defaults-mode-node": "^2.0.25",
- "@smithy/util-endpoints": "^1.0.4",
- "@smithy/util-retry": "^2.0.6",
- "@smithy/util-utf8": "^2.0.2",
- "fast-xml-parser": "4.2.5",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/core": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.451.0.tgz",
- "integrity": "sha512-SamWW2zHEf1ZKe3j1w0Piauryl8BQIlej0TBS18A4ACzhjhWXhCs13bO1S88LvPR5mBFXok3XOT6zPOnKDFktw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/smithy-client": "^2.1.15",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-cognito-identity": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.451.0.tgz",
- "integrity": "sha512-g1ZT46NuYfou00d94rJZ59N4TLI1T+v46lbHTtF9jwohiUsi7/vHkPIOdrgtrThGzGUVl01w62N0a2mpMydaBA==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-sdk/client-cognito-identity": "3.451.0",
- "@aws-sdk/types": "3.451.0",
- "@smithy/property-provider": "^2.0.0",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-env": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.451.0.tgz",
- "integrity": "sha512-9dAav7DcRgaF7xCJEQR5ER9ErXxnu/tdnVJ+UPmb1NPeIZdESv1A3lxFDEq1Fs8c4/lzAj9BpshGyJVIZwZDKg==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-sdk/types": "3.451.0",
- "@smithy/property-provider": "^2.0.0",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-http": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.451.0.tgz",
- "integrity": "sha512-q82kEzymqimkJ2dHmuN2RGpi9HTFSxwwoXALnzPRaRcvR/v+YY8FMgSTfwXzPkHUDf/q8J+aDz6lPcYlnsP3sQ==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-sdk/types": "3.451.0",
- "@smithy/fetch-http-handler": "^2.2.6",
- "@smithy/node-http-handler": "^2.1.9",
- "@smithy/property-provider": "^2.0.0",
- "@smithy/protocol-http": "^3.0.9",
- "@smithy/smithy-client": "^2.1.15",
- "@smithy/types": "^2.5.0",
- "@smithy/util-stream": "^2.0.20",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-ini": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.451.0.tgz",
- "integrity": "sha512-TySt64Ci5/ZbqFw1F9Z0FIGvYx5JSC9e6gqDnizIYd8eMnn8wFRUscRrD7pIHKfrhvVKN5h0GdYovmMO/FMCBw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-sdk/credential-provider-env": "3.451.0",
- "@aws-sdk/credential-provider-process": "3.451.0",
- "@aws-sdk/credential-provider-sso": "3.451.0",
- "@aws-sdk/credential-provider-web-identity": "3.451.0",
- "@aws-sdk/types": "3.451.0",
- "@smithy/credential-provider-imds": "^2.0.0",
- "@smithy/property-provider": "^2.0.0",
- "@smithy/shared-ini-file-loader": "^2.0.6",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-node": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.451.0.tgz",
- "integrity": "sha512-AEwM1WPyxUdKrKyUsKyFqqRFGU70e4qlDyrtBxJnSU9NRLZI8tfEZ67bN7fHSxBUBODgDXpMSlSvJiBLh5/3pw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-sdk/credential-provider-env": "3.451.0",
- "@aws-sdk/credential-provider-ini": "3.451.0",
- "@aws-sdk/credential-provider-process": "3.451.0",
- "@aws-sdk/credential-provider-sso": "3.451.0",
- "@aws-sdk/credential-provider-web-identity": "3.451.0",
- "@aws-sdk/types": "3.451.0",
- "@smithy/credential-provider-imds": "^2.0.0",
- "@smithy/property-provider": "^2.0.0",
- "@smithy/shared-ini-file-loader": "^2.0.6",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-process": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.451.0.tgz",
- "integrity": "sha512-HQywSdKeD5PErcLLnZfSyCJO+6T+ZyzF+Lm/QgscSC+CbSUSIPi//s15qhBRVely/3KBV6AywxwNH+5eYgt4lQ==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-sdk/types": "3.451.0",
- "@smithy/property-provider": "^2.0.0",
- "@smithy/shared-ini-file-loader": "^2.0.6",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-sso": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.451.0.tgz",
- "integrity": "sha512-Usm/N51+unOt8ID4HnQzxIjUJDrkAQ1vyTOC0gSEEJ7h64NSSPGD5yhN7il5WcErtRd3EEtT1a8/GTC5TdBctg==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-sdk/client-sso": "3.451.0",
- "@aws-sdk/token-providers": "3.451.0",
- "@aws-sdk/types": "3.451.0",
- "@smithy/property-provider": "^2.0.0",
- "@smithy/shared-ini-file-loader": "^2.0.6",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-provider-web-identity": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.451.0.tgz",
- "integrity": "sha512-Xtg3Qw65EfDjWNG7o2xD6sEmumPfsy3WDGjk2phEzVg8s7hcZGxf5wYwe6UY7RJvlEKrU0rFA+AMn6Hfj5oOzg==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-sdk/types": "3.451.0",
- "@smithy/property-provider": "^2.0.0",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/credential-providers": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.451.0.tgz",
- "integrity": "sha512-ihbYZrI/tSVsZFDGLfJoCx3sg1s9EQqWA+xbLoquK+RjMqTnaeshYntFJmQA5yqCIbcAkyw63OwOIBRrVb7tMA==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-sdk/client-cognito-identity": "3.451.0",
- "@aws-sdk/client-sso": "3.451.0",
- "@aws-sdk/client-sts": "3.451.0",
- "@aws-sdk/credential-provider-cognito-identity": "3.451.0",
- "@aws-sdk/credential-provider-env": "3.451.0",
- "@aws-sdk/credential-provider-http": "3.451.0",
- "@aws-sdk/credential-provider-ini": "3.451.0",
- "@aws-sdk/credential-provider-node": "3.451.0",
- "@aws-sdk/credential-provider-process": "3.451.0",
- "@aws-sdk/credential-provider-sso": "3.451.0",
- "@aws-sdk/credential-provider-web-identity": "3.451.0",
- "@aws-sdk/types": "3.451.0",
- "@smithy/credential-provider-imds": "^2.0.0",
- "@smithy/property-provider": "^2.0.0",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-host-header": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.451.0.tgz",
- "integrity": "sha512-j8a5jAfhWmsK99i2k8oR8zzQgXrsJtgrLxc3js6U+525mcZytoiDndkWTmD5fjJ1byU1U2E5TaPq+QJeDip05Q==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-sdk/types": "3.451.0",
- "@smithy/protocol-http": "^3.0.9",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-logger": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.451.0.tgz",
- "integrity": "sha512-0kHrYEyVeB2QBfP6TfbI240aRtatLZtcErJbhpiNUb+CQPgEL3crIjgVE8yYiJumZ7f0jyjo8HLPkwD1/2APaw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-sdk/types": "3.451.0",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-recursion-detection": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.451.0.tgz",
- "integrity": "sha512-J6jL6gJ7orjHGM70KDRcCP7so/J2SnkN4vZ9YRLTeeZY6zvBuHDjX8GCIgSqPn/nXFXckZO8XSnA7u6+3TAT0w==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-sdk/types": "3.451.0",
- "@smithy/protocol-http": "^3.0.9",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-sdk-sts": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.451.0.tgz",
- "integrity": "sha512-UJ6UfVUEgp0KIztxpAeelPXI5MLj9wUtUCqYeIMP7C1ZhoEMNm3G39VLkGN43dNhBf1LqjsV9jkKMZbVfYXuwg==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-sdk/middleware-signing": "3.451.0",
- "@aws-sdk/types": "3.451.0",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-signing": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.451.0.tgz",
- "integrity": "sha512-s5ZlcIoLNg1Huj4Qp06iKniE8nJt/Pj1B/fjhWc6cCPCM7XJYUCejCnRh6C5ZJoBEYodjuwZBejPc1Wh3j+znA==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-sdk/types": "3.451.0",
- "@smithy/property-provider": "^2.0.0",
- "@smithy/protocol-http": "^3.0.9",
- "@smithy/signature-v4": "^2.0.0",
- "@smithy/types": "^2.5.0",
- "@smithy/util-middleware": "^2.0.6",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/middleware-user-agent": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.451.0.tgz",
- "integrity": "sha512-8NM/0JiKLNvT9wtAQVl1DFW0cEO7OvZyLSUBLNLTHqyvOZxKaZ8YFk7d8PL6l76LeUKRxq4NMxfZQlUIRe0eSA==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-sdk/types": "3.451.0",
- "@aws-sdk/util-endpoints": "3.451.0",
- "@smithy/protocol-http": "^3.0.9",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/region-config-resolver": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.451.0.tgz",
- "integrity": "sha512-3iMf4OwzrFb4tAAmoROXaiORUk2FvSejnHIw/XHvf/jjR4EqGGF95NZP/n/MeFZMizJWVssrwS412GmoEyoqhg==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/node-config-provider": "^2.1.5",
- "@smithy/types": "^2.5.0",
- "@smithy/util-config-provider": "^2.0.0",
- "@smithy/util-middleware": "^2.0.6",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/token-providers": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.451.0.tgz",
- "integrity": "sha512-ij1L5iUbn6CwxVOT1PG4NFjsrsKN9c4N1YEM0lkl6DwmaNOscjLKGSNyj9M118vSWsOs1ZDbTwtj++h0O/BWrQ==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-crypto/sha256-browser": "3.0.0",
- "@aws-crypto/sha256-js": "3.0.0",
- "@aws-sdk/middleware-host-header": "3.451.0",
- "@aws-sdk/middleware-logger": "3.451.0",
- "@aws-sdk/middleware-recursion-detection": "3.451.0",
- "@aws-sdk/middleware-user-agent": "3.451.0",
- "@aws-sdk/region-config-resolver": "3.451.0",
- "@aws-sdk/types": "3.451.0",
- "@aws-sdk/util-endpoints": "3.451.0",
- "@aws-sdk/util-user-agent-browser": "3.451.0",
- "@aws-sdk/util-user-agent-node": "3.451.0",
- "@smithy/config-resolver": "^2.0.18",
- "@smithy/fetch-http-handler": "^2.2.6",
- "@smithy/hash-node": "^2.0.15",
- "@smithy/invalid-dependency": "^2.0.13",
- "@smithy/middleware-content-length": "^2.0.15",
- "@smithy/middleware-endpoint": "^2.2.0",
- "@smithy/middleware-retry": "^2.0.20",
- "@smithy/middleware-serde": "^2.0.13",
- "@smithy/middleware-stack": "^2.0.7",
- "@smithy/node-config-provider": "^2.1.5",
- "@smithy/node-http-handler": "^2.1.9",
- "@smithy/property-provider": "^2.0.0",
- "@smithy/protocol-http": "^3.0.9",
- "@smithy/shared-ini-file-loader": "^2.0.6",
- "@smithy/smithy-client": "^2.1.15",
- "@smithy/types": "^2.5.0",
- "@smithy/url-parser": "^2.0.13",
- "@smithy/util-base64": "^2.0.1",
- "@smithy/util-body-length-browser": "^2.0.0",
- "@smithy/util-body-length-node": "^2.1.0",
- "@smithy/util-defaults-mode-browser": "^2.0.19",
- "@smithy/util-defaults-mode-node": "^2.0.25",
- "@smithy/util-endpoints": "^1.0.4",
- "@smithy/util-retry": "^2.0.6",
- "@smithy/util-utf8": "^2.0.2",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/types": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.451.0.tgz",
- "integrity": "sha512-rhK+qeYwCIs+laJfWCcrYEjay2FR/9VABZJ2NRM89jV/fKqGVQR52E5DQqrI+oEIL5JHMhhnr4N4fyECMS35lw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/util-endpoints": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.451.0.tgz",
- "integrity": "sha512-giqLGBTnRIcKkDqwU7+GQhKbtJ5Ku35cjGQIfMyOga6pwTBUbaK0xW1Sdd8sBQ1GhApscnChzI9o/R9x0368vw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-sdk/types": "3.451.0",
- "@smithy/util-endpoints": "^1.0.4",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/util-locate-window": {
- "version": "3.310.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.310.0.tgz",
- "integrity": "sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@aws-sdk/util-user-agent-browser": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.451.0.tgz",
- "integrity": "sha512-Ws5mG3J0TQifH7OTcMrCTexo7HeSAc3cBgjfhS/ofzPUzVCtsyg0G7I6T7wl7vJJETix2Kst2cpOsxygPgPD9w==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-sdk/types": "3.451.0",
- "@smithy/types": "^2.5.0",
- "bowser": "^2.11.0",
- "tslib": "^2.5.0"
- }
- },
- "node_modules/@aws-sdk/util-user-agent-node": {
- "version": "3.451.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.451.0.tgz",
- "integrity": "sha512-TBzm6P+ql4mkGFAjPlO1CI+w3yUT+NulaiALjl/jNX/nnUp6HsJsVxJf4nVFQTG5KRV0iqMypcs7I3KIhH+LmA==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-sdk/types": "3.451.0",
- "@smithy/node-config-provider": "^2.1.5",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- },
- "peerDependencies": {
- "aws-crt": ">=1.0.0"
- },
- "peerDependenciesMeta": {
- "aws-crt": {
- "optional": true
- }
- }
- },
- "node_modules/@aws-sdk/util-utf8-browser": {
- "version": "3.259.0",
- "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz",
- "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "tslib": "^2.3.1"
- }
- },
- "node_modules/@babel/helper-string-parser": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz",
- "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==",
- "dev": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.24.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz",
- "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==",
- "dev": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "7.24.5",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz",
- "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==",
- "dev": true,
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@babel/runtime": {
- "version": "7.23.9",
- "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.9.tgz",
- "integrity": "sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==",
- "dependencies": {
- "regenerator-runtime": "^0.14.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/types": {
- "version": "7.24.5",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz",
- "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==",
- "dev": true,
- "dependencies": {
- "@babel/helper-string-parser": "^7.24.1",
- "@babel/helper-validator-identifier": "^7.24.5",
- "to-fast-properties": "^2.0.0"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@bcoe/v8-coverage": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
- "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
- "dev": true
- },
- "node_modules/@colors/colors": {
- "version": "1.5.0",
- "license": "MIT",
- "engines": {
- "node": ">=0.1.90"
- }
- },
- "node_modules/@cspotcode/source-map-support": {
- "version": "0.8.1",
- "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
- "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
- "dev": true,
- "dependencies": {
- "@jridgewell/trace-mapping": "0.3.9"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@dabh/diagnostics": {
- "version": "2.0.3",
- "license": "MIT",
- "dependencies": {
- "colorspace": "1.1.x",
- "enabled": "2.0.x",
- "kuler": "^2.0.0"
- }
- },
- "node_modules/@date-fns/utc": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@date-fns/utc/-/utc-1.2.0.tgz",
- "integrity": "sha512-YLq+crMPJiBmIdkRmv9nZuZy1mVtMlDcUKlg4mvI0UsC/dZeIaGoGB5p/C4FrpeOhZ7zBTK03T58S0DFkRNMnw=="
- },
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
- "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz",
- "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz",
- "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz",
- "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz",
- "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz",
- "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz",
- "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz",
- "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz",
- "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz",
- "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz",
- "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz",
- "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz",
- "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz",
- "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz",
- "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz",
- "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz",
- "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz",
- "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz",
- "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz",
- "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz",
- "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz",
- "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz",
- "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@fastify/ajv-compiler": {
- "version": "1.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^6.12.6"
- }
- },
- "node_modules/@fastify/busboy": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.2.1.tgz",
- "integrity": "sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==",
- "dependencies": {
- "text-decoding": "^1.0.0"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@firebase/app-check-interop-types": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.0.tgz",
- "integrity": "sha512-xAxHPZPIgFXnI+vb4sbBjZcde7ZluzPPaSK7Lx3/nmuVk4TjZvnL8ONnkd4ERQKL8WePQySU+pRcWkh8rDf5Sg=="
- },
- "node_modules/@firebase/app-types": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz",
- "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q=="
- },
- "node_modules/@firebase/auth-interop-types": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz",
- "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg=="
- },
- "node_modules/@firebase/component": {
- "version": "0.6.5",
- "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.5.tgz",
- "integrity": "sha512-2tVDk1ixi12sbDmmfITK8lxSjmcb73BMF6Qwc3U44hN/J1Fi1QY/Hnnb6klFlbB9/G16a3J3d4nXykye2EADTw==",
- "dependencies": {
- "@firebase/util": "1.9.4",
- "tslib": "^2.1.0"
- }
- },
- "node_modules/@firebase/database": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.3.tgz",
- "integrity": "sha512-9fjqLt9JzL46gw9+NRqsgQEMjgRwfd8XtzcKqG+UYyhVeFCdVRQ0Wp6Dw/dvYHnbH5vNEKzNv36dcB4p+PIAAA==",
- "dependencies": {
- "@firebase/app-check-interop-types": "0.3.0",
- "@firebase/auth-interop-types": "0.2.1",
- "@firebase/component": "0.6.5",
- "@firebase/logger": "0.4.0",
- "@firebase/util": "1.9.4",
- "faye-websocket": "0.11.4",
- "tslib": "^2.1.0"
- }
- },
- "node_modules/@firebase/database-compat": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-1.0.3.tgz",
- "integrity": "sha512-7tHEOcMbK5jJzHWyphPux4osogH/adWwncxdMxdBpB9g1DNIyY4dcz1oJdlkXGM/i/AjUBesZsd5CuwTRTBNTw==",
- "dependencies": {
- "@firebase/component": "0.6.5",
- "@firebase/database": "1.0.3",
- "@firebase/database-types": "1.0.1",
- "@firebase/logger": "0.4.0",
- "@firebase/util": "1.9.4",
- "tslib": "^2.1.0"
- }
- },
- "node_modules/@firebase/database-types": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.1.tgz",
- "integrity": "sha512-Tmcmx5XgiI7UVF/4oGg2P3AOTfq3WKEPsm2yf+uXtN7uG/a4WTWhVMrXGYRY2ZUL1xPxv9V33wQRJ+CcrUhVXw==",
- "dependencies": {
- "@firebase/app-types": "0.9.0",
- "@firebase/util": "1.9.4"
- }
- },
- "node_modules/@firebase/logger": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz",
- "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==",
- "dependencies": {
- "tslib": "^2.1.0"
- }
- },
- "node_modules/@firebase/util": {
- "version": "1.9.4",
- "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.4.tgz",
- "integrity": "sha512-WLonYmS1FGHT97TsUmRN3qnTh5TeeoJp1Gg5fithzuAgdZOUtsYECfy7/noQ3llaguios8r5BuXSEiK82+UrxQ==",
- "dependencies": {
- "tslib": "^2.1.0"
- }
- },
- "node_modules/@google-cloud/firestore": {
- "version": "7.3.0",
- "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.3.0.tgz",
- "integrity": "sha512-2IftQLAbCuVp0nTd3neeu+d3OYIegJpV/V9R4USQj51LzJcXPe8h8jZ7j3+svSNhJVGy6JsN0T1QqlJdMDhTwg==",
- "optional": true,
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "functional-red-black-tree": "^1.0.1",
- "google-gax": "^4.0.4",
- "protobufjs": "^7.2.5"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@google-cloud/paginator": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.0.tgz",
- "integrity": "sha512-87aeg6QQcEPxGCOthnpUjvw4xAZ57G7pL8FS0C4e/81fr3FjkpUpibf1s2v5XGyGhUVGF4Jfg7yEcxqn2iUw1w==",
- "optional": true,
- "dependencies": {
- "arrify": "^2.0.0",
- "extend": "^3.0.2"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@google-cloud/projectify": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz",
- "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==",
- "optional": true,
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@google-cloud/promisify": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz",
- "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==",
- "optional": true,
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@google-cloud/storage": {
- "version": "7.7.0",
- "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.7.0.tgz",
- "integrity": "sha512-EMCEY+6JiIkx7Dt8NXVGGjy1vRdSGdHkoqZoqjJw7cEBkT7ZkX0c7puedfn1MamnzW5SX4xoa2jVq5u7OWBmkQ==",
- "optional": true,
- "dependencies": {
- "@google-cloud/paginator": "^5.0.0",
- "@google-cloud/projectify": "^4.0.0",
- "@google-cloud/promisify": "^4.0.0",
- "abort-controller": "^3.0.0",
- "async-retry": "^1.3.3",
- "compressible": "^2.0.12",
- "duplexify": "^4.0.0",
- "ent": "^2.2.0",
- "fast-xml-parser": "^4.3.0",
- "gaxios": "^6.0.2",
- "google-auth-library": "^9.0.0",
- "mime": "^3.0.0",
- "mime-types": "^2.0.8",
- "p-limit": "^3.0.1",
- "retry-request": "^7.0.0",
- "teeny-request": "^9.0.0",
- "uuid": "^8.0.0"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@google-cloud/storage/node_modules/fast-xml-parser": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.4.tgz",
- "integrity": "sha512-utnwm92SyozgA3hhH2I8qldf2lBqm6qHOICawRNRFu1qMe3+oqr+GcXjGqTmXTMGE5T4eC03kr/rlh5C1IRdZA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/NaturalIntelligence"
- },
- {
- "type": "paypal",
- "url": "https://paypal.me/naturalintelligence"
- }
- ],
- "optional": true,
- "dependencies": {
- "strnum": "^1.0.5"
- },
- "bin": {
- "fxparser": "src/cli/cli.js"
- }
- },
- "node_modules/@google-cloud/storage/node_modules/mime": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
- "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
- "optional": true,
- "bin": {
- "mime": "cli.js"
- },
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/@google-cloud/storage/node_modules/uuid": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
- "optional": true,
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
- "node_modules/@grpc/grpc-js": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.10.0.tgz",
- "integrity": "sha512-tx+eoEsqkMkLCHR4OOplwNIaJ7SVZWzeVKzEMBz8VR+TbssgBYOP4a0P+KQiQ6LaTG4SGaIEu7YTS8xOmkOWLA==",
- "optional": true,
- "dependencies": {
- "@grpc/proto-loader": "^0.7.8",
- "@types/node": ">=12.12.47"
- },
- "engines": {
- "node": "^8.13.0 || >=10.10.0"
- }
- },
- "node_modules/@grpc/proto-loader": {
- "version": "0.7.10",
- "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.10.tgz",
- "integrity": "sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ==",
- "optional": true,
- "dependencies": {
- "lodash.camelcase": "^4.3.0",
- "long": "^5.0.0",
- "protobufjs": "^7.2.4",
- "yargs": "^17.7.2"
- },
- "bin": {
- "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/@hapi/b64": {
- "version": "5.0.0",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@hapi/hoek": "9.x.x"
- }
- },
- "node_modules/@hapi/boom": {
- "version": "9.1.4",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@hapi/hoek": "9.x.x"
- }
- },
- "node_modules/@hapi/bourne": {
- "version": "2.0.0",
- "dev": true,
- "license": "BSD-3-Clause"
- },
- "node_modules/@hapi/cryptiles": {
- "version": "5.1.0",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@hapi/boom": "9.x.x"
- },
- "engines": {
- "node": ">=12.0.0"
- }
- },
- "node_modules/@hapi/hoek": {
- "version": "9.2.1",
- "license": "BSD-3-Clause"
- },
- "node_modules/@hapi/iron": {
- "version": "6.0.0",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@hapi/b64": "5.x.x",
- "@hapi/boom": "9.x.x",
- "@hapi/bourne": "2.x.x",
- "@hapi/cryptiles": "5.x.x",
- "@hapi/hoek": "9.x.x"
- }
- },
- "node_modules/@hapi/podium": {
- "version": "4.1.3",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@hapi/hoek": "9.x.x",
- "@hapi/teamwork": "5.x.x",
- "@hapi/validate": "1.x.x"
- }
- },
- "node_modules/@hapi/teamwork": {
- "version": "5.1.0",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=12.0.0"
- }
- },
- "node_modules/@hapi/topo": {
- "version": "5.1.0",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@hapi/hoek": "^9.0.0"
- }
- },
- "node_modules/@hapi/validate": {
- "version": "1.1.3",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@hapi/hoek": "^9.0.0",
- "@hapi/topo": "^5.0.0"
- }
- },
- "node_modules/@ioredis/commands": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
- "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="
- },
- "node_modules/@isaacs/cliui": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
- "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
- "dependencies": {
- "string-width": "^5.1.2",
- "string-width-cjs": "npm:string-width@^4.2.0",
- "strip-ansi": "^7.0.1",
- "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
- "wrap-ansi": "^8.1.0",
- "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
- "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/ansi-styles": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
- "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
- },
- "node_modules/@isaacs/cliui/node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
- "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
- "dependencies": {
- "ansi-regex": "^6.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
- }
- },
- "node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
- "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
- "dependencies": {
- "ansi-styles": "^6.1.0",
- "string-width": "^5.0.1",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/@istanbuljs/schema": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
- "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@jest/schemas": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
- "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
- "dev": true,
- "dependencies": {
- "@sinclair/typebox": "^0.27.8"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
- "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
- "dev": true,
- "dependencies": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
- "dev": true,
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
- "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
- "dev": true,
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "dev": true,
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.4.15",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
- "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
- "dev": true
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.9",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.0.3",
- "@jridgewell/sourcemap-codec": "^1.4.10"
- }
- },
- "node_modules/@kwsites/file-exists": {
- "version": "1.1.1",
- "license": "MIT",
- "dependencies": {
- "debug": "^4.1.1"
- }
- },
- "node_modules/@kwsites/file-exists/node_modules/debug": {
- "version": "4.3.3",
- "license": "MIT",
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/@kwsites/file-exists/node_modules/ms": {
- "version": "2.1.2",
- "license": "MIT"
- },
- "node_modules/@kwsites/promise-deferred": {
- "version": "1.1.1",
- "license": "MIT"
- },
- "node_modules/@mapbox/node-pre-gyp": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
- "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
- "dependencies": {
- "detect-libc": "^2.0.0",
- "https-proxy-agent": "^5.0.0",
- "make-dir": "^3.1.0",
- "node-fetch": "^2.6.7",
- "nopt": "^5.0.0",
- "npmlog": "^5.0.1",
- "rimraf": "^3.0.2",
- "semver": "^7.3.5",
- "tar": "^6.1.11"
- },
- "bin": {
- "node-pre-gyp": "bin/node-pre-gyp"
- }
- },
- "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
- "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
- "dependencies": {
- "abbrev": "1"
- },
- "bin": {
- "nopt": "bin/nopt.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/@mongodb-js/saslprep": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.1.tgz",
- "integrity": "sha512-t7c5K033joZZMspnHg/gWPE4kandgc2OxE74aYOtGKfgB9VPuVJPix0H6fhmm2erj5PBJ21mqcx34lpIGtUCsQ==",
- "dependencies": {
- "sparse-bitfield": "^3.0.3"
- }
- },
- "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz",
- "integrity": "sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==",
- "cpu": [
- "arm64"
- ],
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@one-ini/wasm": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
- "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw=="
- },
- "node_modules/@pkgjs/parseargs": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
- "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
- "optional": true,
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@protobufjs/aspromise": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
- "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
- "optional": true
- },
- "node_modules/@protobufjs/base64": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
- "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
- "optional": true
- },
- "node_modules/@protobufjs/codegen": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
- "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==",
- "optional": true
- },
- "node_modules/@protobufjs/eventemitter": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
- "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==",
- "optional": true
- },
- "node_modules/@protobufjs/fetch": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
- "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
- "optional": true,
- "dependencies": {
- "@protobufjs/aspromise": "^1.1.1",
- "@protobufjs/inquire": "^1.1.0"
- }
- },
- "node_modules/@protobufjs/float": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
- "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
- "optional": true
- },
- "node_modules/@protobufjs/inquire": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
- "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==",
- "optional": true
- },
- "node_modules/@protobufjs/path": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
- "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
- "optional": true
- },
- "node_modules/@protobufjs/pool": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
- "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
- "optional": true
- },
- "node_modules/@protobufjs/utf8": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
- "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
- "optional": true
- },
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz",
- "integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz",
- "integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz",
- "integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz",
- "integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz",
- "integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz",
- "integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz",
- "integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz",
- "integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz",
- "integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz",
- "integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz",
- "integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz",
- "integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz",
- "integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz",
- "integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz",
- "integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz",
- "integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@sideway/address": {
- "version": "4.1.3",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@hapi/hoek": "^9.0.0"
- }
- },
- "node_modules/@sideway/formula": {
- "version": "3.0.0",
- "license": "BSD-3-Clause"
- },
- "node_modules/@sideway/pinpoint": {
- "version": "2.0.0",
- "license": "BSD-3-Clause"
- },
- "node_modules/@sinclair/typebox": {
- "version": "0.27.8",
- "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
- "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
- "dev": true
- },
- "node_modules/@smithy/abort-controller": {
- "version": "2.0.13",
- "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.0.13.tgz",
- "integrity": "sha512-eeOPD+GF9BzF/Mjy3PICLePx4l0f3rG/nQegQHRLTloN5p1lSJJNZsyn+FzDnW8P2AduragZqJdtKNCxXozB1Q==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/config-resolver": {
- "version": "2.0.18",
- "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.0.18.tgz",
- "integrity": "sha512-761sJSgNbvsqcsKW6/WZbrZr4H+0Vp/QKKqwyrxCPwD8BsiPEXNHyYnqNgaeK9xRWYswjon0Uxbpe3DWQo0j/g==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/node-config-provider": "^2.1.5",
- "@smithy/types": "^2.5.0",
- "@smithy/util-config-provider": "^2.0.0",
- "@smithy/util-middleware": "^2.0.6",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/credential-provider-imds": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.1.1.tgz",
- "integrity": "sha512-gw5G3FjWC6sNz8zpOJgPpH5HGKrpoVFQpToNAwLwJVyI/LJ2jDJRjSKEsM6XI25aRpYjMSE/Qptxx305gN1vHw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/node-config-provider": "^2.1.5",
- "@smithy/property-provider": "^2.0.14",
- "@smithy/types": "^2.5.0",
- "@smithy/url-parser": "^2.0.13",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/eventstream-codec": {
- "version": "2.0.13",
- "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.0.13.tgz",
- "integrity": "sha512-CExbelIYp+DxAHG8RIs0l9QL7ElqhG4ym9BNoSpkPa4ptBQfzJdep3LbOSVJIE2VUdBAeObdeL6EDB3Jo85n3g==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@aws-crypto/crc32": "3.0.0",
- "@smithy/types": "^2.5.0",
- "@smithy/util-hex-encoding": "^2.0.0",
- "tslib": "^2.5.0"
- }
- },
- "node_modules/@smithy/fetch-http-handler": {
- "version": "2.2.6",
- "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.2.6.tgz",
- "integrity": "sha512-PStY3XO1Ksjwn3wMKye5U6m6zxXpXrXZYqLy/IeCbh3nM9QB3Jgw/B0PUSLUWKdXg4U8qgEu300e3ZoBvZLsDg==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/protocol-http": "^3.0.9",
- "@smithy/querystring-builder": "^2.0.13",
- "@smithy/types": "^2.5.0",
- "@smithy/util-base64": "^2.0.1",
- "tslib": "^2.5.0"
- }
- },
- "node_modules/@smithy/hash-node": {
- "version": "2.0.15",
- "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.0.15.tgz",
- "integrity": "sha512-t/qjEJZu/G46A22PAk1k/IiJZT4ncRkG5GOCNWN9HPPy5rCcSZUbh7gwp7CGKgJJ7ATMMg+0Td7i9o1lQTwOfQ==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/types": "^2.5.0",
- "@smithy/util-buffer-from": "^2.0.0",
- "@smithy/util-utf8": "^2.0.2",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/invalid-dependency": {
- "version": "2.0.13",
- "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.0.13.tgz",
- "integrity": "sha512-XsGYhVhvEikX1Yz0kyIoLssJf2Rs6E0U2w2YuKdT4jSra5A/g8V2oLROC1s56NldbgnpesTYB2z55KCHHbKyjw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- }
- },
- "node_modules/@smithy/is-array-buffer": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz",
- "integrity": "sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/middleware-content-length": {
- "version": "2.0.15",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.0.15.tgz",
- "integrity": "sha512-xH4kRBw01gJgWiU+/mNTrnyFXeozpZHw39gLb3JKGsFDVmSrJZ8/tRqu27tU/ki1gKkxr2wApu+dEYjI3QwV1Q==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/protocol-http": "^3.0.9",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/middleware-endpoint": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.2.0.tgz",
- "integrity": "sha512-tddRmaig5URk2106PVMiNX6mc5BnKIKajHHDxb7K0J5MLdcuQluHMGnjkv18iY9s9O0tF+gAcPd/pDXA5L9DZw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/middleware-serde": "^2.0.13",
- "@smithy/node-config-provider": "^2.1.5",
- "@smithy/shared-ini-file-loader": "^2.2.4",
- "@smithy/types": "^2.5.0",
- "@smithy/url-parser": "^2.0.13",
- "@smithy/util-middleware": "^2.0.6",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/middleware-retry": {
- "version": "2.0.20",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.0.20.tgz",
- "integrity": "sha512-X2yrF/SHDk2WDd8LflRNS955rlzQ9daz9UWSp15wW8KtzoTXg3bhHM78HbK1cjr48/FWERSJKh9AvRUUGlIawg==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/node-config-provider": "^2.1.5",
- "@smithy/protocol-http": "^3.0.9",
- "@smithy/service-error-classification": "^2.0.6",
- "@smithy/types": "^2.5.0",
- "@smithy/util-middleware": "^2.0.6",
- "@smithy/util-retry": "^2.0.6",
- "tslib": "^2.5.0",
- "uuid": "^8.3.2"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/middleware-retry/node_modules/uuid": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
- "optional": true,
- "peer": true,
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
- "node_modules/@smithy/middleware-serde": {
- "version": "2.0.13",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.0.13.tgz",
- "integrity": "sha512-tBGbeXw+XsE6pPr4UaXOh+UIcXARZeiA8bKJWxk2IjJcD1icVLhBSUQH9myCIZLNNzJIH36SDjUX8Wqk4xJCJg==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/middleware-stack": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.0.7.tgz",
- "integrity": "sha512-L1KLAAWkXbGx1t2jjCI/mDJ2dDNq+rp4/ifr/HcC6FHngxho5O7A5bQLpKHGlkfATH6fUnOEx0VICEVFA4sUzw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/node-config-provider": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.1.5.tgz",
- "integrity": "sha512-3Omb5/h4tOCuKRx4p4pkYTvEYRCYoKk52bOYbKUyz/G/8gERbagsN8jFm4FjQubkrcIqQEghTpQaUw6uk+0edw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/property-provider": "^2.0.14",
- "@smithy/shared-ini-file-loader": "^2.2.4",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/node-http-handler": {
- "version": "2.1.9",
- "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.1.9.tgz",
- "integrity": "sha512-+K0q3SlNcocmo9OZj+fz67gY4lwhOCvIJxVbo/xH+hfWObvaxrMTx7JEzzXcluK0thnnLz++K3Qe7Z/8MDUreA==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/abort-controller": "^2.0.13",
- "@smithy/protocol-http": "^3.0.9",
- "@smithy/querystring-builder": "^2.0.13",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/property-provider": {
- "version": "2.0.14",
- "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.0.14.tgz",
- "integrity": "sha512-k3D2qp9o6imTrLaXRj6GdLYEJr1sXqS99nLhzq8fYmJjSVOeMg/G+1KVAAc7Oxpu71rlZ2f8SSZxcSxkevuR0A==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/protocol-http": {
- "version": "3.0.9",
- "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.0.9.tgz",
- "integrity": "sha512-U1wl+FhYu4/BC+rjwh1lg2gcJChQhytiNQSggREgQ9G2FzmoK9sACBZvx7thyWMvRyHQTE22mO2d5UM8gMKDBg==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/querystring-builder": {
- "version": "2.0.13",
- "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.0.13.tgz",
- "integrity": "sha512-JhXKwp3JtsFUe96XLHy/nUPEbaXqn6r7xE4sNaH8bxEyytE5q1fwt0ew/Ke6+vIC7gP87HCHgQpJHg1X1jN2Fw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/types": "^2.5.0",
- "@smithy/util-uri-escape": "^2.0.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/querystring-parser": {
- "version": "2.0.13",
- "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.0.13.tgz",
- "integrity": "sha512-TEiT6o8CPZVxJ44Rly/rrsATTQsE+b/nyBVzsYn2sa75xAaZcurNxsFd8z1haoUysONiyex24JMHoJY6iCfLdA==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/service-error-classification": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.0.6.tgz",
- "integrity": "sha512-fCQ36frtYra2fqY2/DV8+3/z2d0VB/1D1hXbjRcM5wkxTToxq6xHbIY/NGGY6v4carskMyG8FHACxgxturJ9Pg==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/types": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/shared-ini-file-loader": {
- "version": "2.2.4",
- "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.2.4.tgz",
- "integrity": "sha512-9dRknGgvYlRIsoTcmMJXuoR/3ekhGwhRq4un3ns2/byre4Ql5hyUN4iS0x8eITohjU90YOnUCsbRwZRvCkbRfw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/signature-v4": {
- "version": "2.0.15",
- "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.0.15.tgz",
- "integrity": "sha512-SRTEJSEhQYVlBKIIdZ9SZpqW+KFqxqcNnEcBX+8xkDdWx+DItme9VcCDkdN32yTIrICC+irUufnUdV7mmHPjoA==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/eventstream-codec": "^2.0.13",
- "@smithy/is-array-buffer": "^2.0.0",
- "@smithy/types": "^2.5.0",
- "@smithy/util-hex-encoding": "^2.0.0",
- "@smithy/util-middleware": "^2.0.6",
- "@smithy/util-uri-escape": "^2.0.0",
- "@smithy/util-utf8": "^2.0.2",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/smithy-client": {
- "version": "2.1.15",
- "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.1.15.tgz",
- "integrity": "sha512-rngZcQu7Jvs9UbHihK1EI67RMPuzkc3CJmu4MBgB7D7yBnMGuFR86tq5rqHfL2gAkNnMelBN/8kzQVvZjNKefQ==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/middleware-stack": "^2.0.7",
- "@smithy/types": "^2.5.0",
- "@smithy/util-stream": "^2.0.20",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/types": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.5.0.tgz",
- "integrity": "sha512-/a31lYofrMBkJb3BuPlYJTMKDj0hUmKUP6JFZQu6YVuQVoAjubiY0A52U9S0Uysd33n/djexCUSNJ+G9bf3/aA==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/url-parser": {
- "version": "2.0.13",
- "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.0.13.tgz",
- "integrity": "sha512-okWx2P/d9jcTsZWTVNnRMpFOE7fMkzloSFyM53fA7nLKJQObxM2T4JlZ5KitKKuXq7pxon9J6SF2kCwtdflIrA==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/querystring-parser": "^2.0.13",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- }
- },
- "node_modules/@smithy/util-base64": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.0.1.tgz",
- "integrity": "sha512-DlI6XFYDMsIVN+GH9JtcRp3j02JEVuWIn/QOZisVzpIAprdsxGveFed0bjbMRCqmIFe8uetn5rxzNrBtIGrPIQ==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/util-buffer-from": "^2.0.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/util-body-length-browser": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.0.0.tgz",
- "integrity": "sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "tslib": "^2.5.0"
- }
- },
- "node_modules/@smithy/util-body-length-node": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.1.0.tgz",
- "integrity": "sha512-/li0/kj/y3fQ3vyzn36NTLGmUwAICb7Jbe/CsWCktW363gh1MOcpEcSO3mJ344Gv2dqz8YJCLQpb6hju/0qOWw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/util-buffer-from": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz",
- "integrity": "sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/is-array-buffer": "^2.0.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/util-config-provider": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.0.0.tgz",
- "integrity": "sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/util-defaults-mode-browser": {
- "version": "2.0.19",
- "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.0.19.tgz",
- "integrity": "sha512-VHP8xdFR7/orpiABJwgoTB0t8Zhhwpf93gXhNfUBiwAE9O0rvsv7LwpQYjgvbOUDDO8JfIYQB2GYJNkqqGWsXw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/property-provider": "^2.0.14",
- "@smithy/smithy-client": "^2.1.15",
- "@smithy/types": "^2.5.0",
- "bowser": "^2.11.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">= 10.0.0"
- }
- },
- "node_modules/@smithy/util-defaults-mode-node": {
- "version": "2.0.25",
- "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.0.25.tgz",
- "integrity": "sha512-jkmep6/JyWmn2ADw9VULDeGbugR4N/FJCKOt+gYyVswmN1BJOfzF2umaYxQ1HhQDvna3kzm1Dbo1qIfBW4iuHA==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/config-resolver": "^2.0.18",
- "@smithy/credential-provider-imds": "^2.1.1",
- "@smithy/node-config-provider": "^2.1.5",
- "@smithy/property-provider": "^2.0.14",
- "@smithy/smithy-client": "^2.1.15",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">= 10.0.0"
- }
- },
- "node_modules/@smithy/util-endpoints": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.0.4.tgz",
- "integrity": "sha512-FPry8j1xye5yzrdnf4xKUXVnkQErxdN7bUIaqC0OFoGsv2NfD9b2UUMuZSSt+pr9a8XWAqj0HoyVNUfPiZ/PvQ==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/node-config-provider": "^2.1.5",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">= 14.0.0"
- }
- },
- "node_modules/@smithy/util-hex-encoding": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz",
- "integrity": "sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/util-middleware": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.0.6.tgz",
- "integrity": "sha512-7W4uuwBvSLgKoLC1x4LfeArCVcbuHdtVaC4g30kKsD1erfICyQ45+tFhhs/dZNeQg+w392fhunCm/+oCcb6BSA==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/util-retry": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.0.6.tgz",
- "integrity": "sha512-PSO41FofOBmyhPQJwBQJ6mVlaD7Sp9Uff9aBbnfBJ9eqXOE/obrqQjn0PNdkfdvViiPXl49BINfnGcFtSP4kYw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/service-error-classification": "^2.0.6",
- "@smithy/types": "^2.5.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">= 14.0.0"
- }
- },
- "node_modules/@smithy/util-stream": {
- "version": "2.0.20",
- "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.0.20.tgz",
- "integrity": "sha512-tT8VASuD8jJu0yjHEMTCPt1o5E3FVzgdsxK6FQLAjXKqVv5V8InCnc0EOsYrijgspbfDqdAJg7r0o2sySfcHVg==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/fetch-http-handler": "^2.2.6",
- "@smithy/node-http-handler": "^2.1.9",
- "@smithy/types": "^2.5.0",
- "@smithy/util-base64": "^2.0.1",
- "@smithy/util-buffer-from": "^2.0.0",
- "@smithy/util-hex-encoding": "^2.0.0",
- "@smithy/util-utf8": "^2.0.2",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/util-uri-escape": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.0.0.tgz",
- "integrity": "sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@smithy/util-utf8": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.2.tgz",
- "integrity": "sha512-qOiVORSPm6Ce4/Yu6hbSgNHABLP2VMv8QOC3tTDNHHlWY19pPyc++fBTbZPtx6egPXi4HQxKDnMxVxpbtX2GoA==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "@smithy/util-buffer-from": "^2.0.0",
- "tslib": "^2.5.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/@tootallnate/once": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
- "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
- "optional": true,
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@tsconfig/node10": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
- "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
- "dev": true
- },
- "node_modules/@tsconfig/node12": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
- "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
- "dev": true
- },
- "node_modules/@tsconfig/node14": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
- "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
- "dev": true
- },
- "node_modules/@tsconfig/node16": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
- "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
- "dev": true
- },
- "node_modules/@types/bcrypt": {
- "version": "5.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@types/body-parser": {
- "version": "1.19.2",
- "license": "MIT",
- "dependencies": {
- "@types/connect": "*",
- "@types/node": "*"
- }
- },
- "node_modules/@types/caseless": {
- "version": "0.12.5",
- "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz",
- "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==",
- "optional": true
- },
- "node_modules/@types/connect": {
- "version": "3.4.35",
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@types/cookiejar": {
- "version": "2.1.2",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/cors": {
- "version": "2.8.12",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/cron": {
- "version": "1.7.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "moment": ">=2.14.0"
- }
- },
- "node_modules/@types/estree": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
- "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
- "dev": true
- },
- "node_modules/@types/express": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
- "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
- "dependencies": {
- "@types/body-parser": "*",
- "@types/express-serve-static-core": "^4.17.33",
- "@types/qs": "*",
- "@types/serve-static": "*"
- }
- },
- "node_modules/@types/express-serve-static-core": {
- "version": "4.17.41",
- "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz",
- "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==",
- "dependencies": {
- "@types/node": "*",
- "@types/qs": "*",
- "@types/range-parser": "*",
- "@types/send": "*"
- }
- },
- "node_modules/@types/hapi__catbox": {
- "version": "10.2.4",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/hapi__hapi": {
- "version": "20.0.10",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@hapi/boom": "^9.0.0",
- "@hapi/iron": "^6.0.0",
- "@hapi/podium": "^4.1.3",
- "@types/hapi__catbox": "*",
- "@types/hapi__mimos": "*",
- "@types/hapi__shot": "*",
- "@types/node": "*",
- "joi": "^17.3.0"
- }
- },
- "node_modules/@types/hapi__mimos": {
- "version": "4.1.4",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/mime-db": "*"
- }
- },
- "node_modules/@types/hapi__shot": {
- "version": "4.1.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@types/ioredis": {
- "version": "4.28.10",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@types/jsonwebtoken": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz",
- "integrity": "sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==",
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@types/lodash": {
- "version": "4.14.178",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/long": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
- "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
- "optional": true
- },
- "node_modules/@types/mime": {
- "version": "1.3.2",
- "license": "MIT"
- },
- "node_modules/@types/mime-db": {
- "version": "1.43.1",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/mustache": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/@types/mustache/-/mustache-4.2.2.tgz",
- "integrity": "sha512-MUSpfpW0yZbTgjekDbH0shMYBUD+X/uJJJMm9LXN1d5yjl5lCY1vN/eWKD6D1tOtjA6206K0zcIPnUaFMurdNA==",
- "dev": true
- },
- "node_modules/@types/node": {
- "version": "18.19.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.1.tgz",
- "integrity": "sha512-mZJ9V11gG5Vp0Ox2oERpeFDl+JvCwK24PGy76vVY/UgBtjwJWc5rYBThFxmbnYOm9UPZNm6wEl/sxHt2SU7x9A==",
- "dependencies": {
- "undici-types": "~5.26.4"
- }
- },
- "node_modules/@types/node-fetch": {
- "version": "2.6.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/node": "*",
- "form-data": "^3.0.0"
- }
- },
- "node_modules/@types/node-fetch/node_modules/form-data": {
- "version": "3.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/@types/nodemailer": {
- "version": "6.4.7",
- "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.7.tgz",
- "integrity": "sha512-f5qCBGAn/f0qtRcd4SEn88c8Fp3Swct1731X4ryPKqS61/A3LmmzN8zaEz7hneJvpjFbUUgY7lru/B/7ODTazg==",
- "dev": true,
- "dependencies": {
- "@types/node": "*"
- }
- },
- "node_modules/@types/object-hash": {
- "version": "2.2.1",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/qs": {
- "version": "6.9.10",
- "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz",
- "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw=="
- },
- "node_modules/@types/range-parser": {
- "version": "1.2.7",
- "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
- "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="
- },
- "node_modules/@types/readline-sync": {
- "version": "1.4.8",
- "resolved": "https://registry.npmjs.org/@types/readline-sync/-/readline-sync-1.4.8.tgz",
- "integrity": "sha512-BL7xOf0yKLA6baAX6MMOnYkoflUyj/c7y3pqMRfU0va7XlwHAOTOIo4x55P/qLfMsuaYdJJKubToLqRVmRtRZA==",
- "dev": true
- },
- "node_modules/@types/request": {
- "version": "2.48.12",
- "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz",
- "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==",
- "optional": true,
- "dependencies": {
- "@types/caseless": "*",
- "@types/node": "*",
- "@types/tough-cookie": "*",
- "form-data": "^2.5.0"
- }
- },
- "node_modules/@types/send": {
- "version": "0.17.4",
- "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
- "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
- "dependencies": {
- "@types/mime": "^1",
- "@types/node": "*"
- }
- },
- "node_modules/@types/serve-static": {
- "version": "1.13.10",
- "license": "MIT",
- "dependencies": {
- "@types/mime": "^1",
- "@types/node": "*"
- }
- },
- "node_modules/@types/string-similarity": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/@types/string-similarity/-/string-similarity-4.0.0.tgz",
- "integrity": "sha512-dMS4S07fbtY1AILG/RhuwmptmzK1Ql8scmAebOTJ/8iBtK/KI17NwGwKzu1uipjj8Kk+3mfPxum56kKZE93mzQ==",
- "dev": true
- },
- "node_modules/@types/strip-bom": {
- "version": "3.0.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/strip-json-comments": {
- "version": "0.0.30",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/superagent": {
- "version": "4.1.15",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/cookiejar": "*",
- "@types/node": "*"
- }
- },
- "node_modules/@types/supertest": {
- "version": "2.0.12",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/superagent": "*"
- }
- },
- "node_modules/@types/swagger-stats": {
- "version": "0.95.4",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/express": "*",
- "@types/hapi__hapi": "*",
- "@types/node": "*",
- "fastify": ">=2.11.0",
- "prom-client": ">=11.5.3"
- }
- },
- "node_modules/@types/swagger-ui-express": {
- "version": "4.1.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/express": "*",
- "@types/serve-static": "*"
- }
- },
- "node_modules/@types/tough-cookie": {
- "version": "4.0.5",
- "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
- "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
- "optional": true
- },
- "node_modules/@types/ua-parser-js": {
- "version": "0.7.36",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/uuid": {
- "version": "8.3.4",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@types/webidl-conversions": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
- "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="
- },
- "node_modules/@types/whatwg-url": {
- "version": "11.0.4",
- "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.4.tgz",
- "integrity": "sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw==",
- "dependencies": {
- "@types/webidl-conversions": "*"
- }
- },
- "node_modules/@vitest/coverage-v8": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-1.6.0.tgz",
- "integrity": "sha512-KvapcbMY/8GYIG0rlwwOKCVNRc0OL20rrhFkg/CHNzncV03TE2XWvO5w9uZYoxNiMEBacAJt3unSOiZ7svePew==",
- "dev": true,
- "dependencies": {
- "@ampproject/remapping": "^2.2.1",
- "@bcoe/v8-coverage": "^0.2.3",
- "debug": "^4.3.4",
- "istanbul-lib-coverage": "^3.2.2",
- "istanbul-lib-report": "^3.0.1",
- "istanbul-lib-source-maps": "^5.0.4",
- "istanbul-reports": "^3.1.6",
- "magic-string": "^0.30.5",
- "magicast": "^0.3.3",
- "picocolors": "^1.0.0",
- "std-env": "^3.5.0",
- "strip-literal": "^2.0.0",
- "test-exclude": "^6.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- },
- "peerDependencies": {
- "vitest": "1.6.0"
- }
- },
- "node_modules/@vitest/coverage-v8/node_modules/debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "dev": true,
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/@vitest/coverage-v8/node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
- },
- "node_modules/@vitest/expect": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.0.tgz",
- "integrity": "sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==",
- "dev": true,
- "dependencies": {
- "@vitest/spy": "1.6.0",
- "@vitest/utils": "1.6.0",
- "chai": "^4.3.10"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/runner": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.0.tgz",
- "integrity": "sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==",
- "dev": true,
- "dependencies": {
- "@vitest/utils": "1.6.0",
- "p-limit": "^5.0.0",
- "pathe": "^1.1.1"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/runner/node_modules/p-limit": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz",
- "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==",
- "dev": true,
- "dependencies": {
- "yocto-queue": "^1.0.0"
- },
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@vitest/runner/node_modules/yocto-queue": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
- "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
- "dev": true,
- "engines": {
- "node": ">=12.20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@vitest/snapshot": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz",
- "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==",
- "dev": true,
- "dependencies": {
- "magic-string": "^0.30.5",
- "pathe": "^1.1.1",
- "pretty-format": "^29.7.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/spy": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.0.tgz",
- "integrity": "sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==",
- "dev": true,
- "dependencies": {
- "tinyspy": "^2.2.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/@vitest/utils": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.0.tgz",
- "integrity": "sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==",
- "dev": true,
- "dependencies": {
- "diff-sequences": "^29.6.3",
- "estree-walker": "^3.0.3",
- "loupe": "^2.3.7",
- "pretty-format": "^29.7.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/abbrev": {
- "version": "1.1.1",
- "license": "ISC"
- },
- "node_modules/abort-controller": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
- "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
- "optional": true,
- "dependencies": {
- "event-target-shim": "^5.0.0"
- },
- "engines": {
- "node": ">=6.5"
- }
- },
- "node_modules/abstract-logging": {
- "version": "2.0.1",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/accepts": {
- "version": "1.3.8",
- "license": "MIT",
- "dependencies": {
- "mime-types": "~2.1.34",
- "negotiator": "0.6.3"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/acorn": {
- "version": "8.11.3",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
- "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
- "dev": true,
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/acorn-walk": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
- "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
- "dev": true,
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/agent-base": {
- "version": "6.0.2",
- "license": "MIT",
- "dependencies": {
- "debug": "4"
- },
- "engines": {
- "node": ">= 6.0.0"
- }
- },
- "node_modules/agent-base/node_modules/debug": {
- "version": "4.3.3",
- "license": "MIT",
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/agent-base/node_modules/ms": {
- "version": "2.1.2",
- "license": "MIT"
- },
- "node_modules/ajv": {
- "version": "6.12.6",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/ansi-colors": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
- "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/ansi-regex": {
- "version": "5.0.1",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/ansi-styles": {
- "version": "4.3.0",
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/anymatch": {
- "version": "3.1.2",
- "license": "ISC",
- "dependencies": {
- "normalize-path": "^3.0.0",
- "picomatch": "^2.0.4"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/aproba": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
- "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
- },
- "node_modules/archy": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/are-we-there-yet": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
- "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
- "dependencies": {
- "delegates": "^1.0.0",
- "readable-stream": "^3.6.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/arg": {
- "version": "4.1.3",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/array-flatten": {
- "version": "1.1.1",
- "license": "MIT"
- },
- "node_modules/arrify": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
- "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
- "optional": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/asap": {
- "version": "2.0.6",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/assertion-error": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
- "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
- "dev": true,
- "engines": {
- "node": "*"
- }
- },
- "node_modules/async": {
- "version": "3.2.3",
- "license": "MIT"
- },
- "node_modules/async-mutex": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.1.tgz",
- "integrity": "sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA==",
- "dev": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
- "node_modules/async-retry": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz",
- "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==",
- "optional": true,
- "dependencies": {
- "retry": "0.13.1"
- }
- },
- "node_modules/asynckit": {
- "version": "0.4.0",
- "license": "MIT"
- },
- "node_modules/atomic-sleep": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8.0.0"
- }
- },
- "node_modules/avvio": {
- "version": "7.2.4",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "archy": "^1.0.0",
- "debug": "^4.0.0",
- "fastq": "^1.6.1",
- "queue-microtask": "^1.1.2"
- }
- },
- "node_modules/avvio/node_modules/debug": {
- "version": "4.3.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/avvio/node_modules/ms": {
- "version": "2.1.2",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/axios": {
- "version": "1.6.7",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
- "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
- "dependencies": {
- "follow-redirects": "^1.15.4",
- "form-data": "^4.0.0",
- "proxy-from-env": "^1.1.0"
- }
- },
- "node_modules/axios/node_modules/form-data": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
- "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/b4a": {
- "version": "1.6.6",
- "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz",
- "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==",
- "dev": true
- },
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "license": "MIT"
- },
- "node_modules/bare-events": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.2.tgz",
- "integrity": "sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==",
- "dev": true,
- "optional": true
- },
- "node_modules/base64-js": {
- "version": "1.5.1",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT",
- "optional": true
- },
- "node_modules/basic-auth": {
- "version": "2.0.1",
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "5.1.2"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/bcrypt": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",
- "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==",
- "hasInstallScript": true,
- "dependencies": {
- "@mapbox/node-pre-gyp": "^1.0.11",
- "node-addon-api": "^5.0.0"
- },
- "engines": {
- "node": ">= 10.0.0"
- }
- },
- "node_modules/bignumber.js": {
- "version": "9.1.2",
- "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz",
- "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==",
- "optional": true,
- "engines": {
- "node": "*"
- }
- },
- "node_modules/binary-extensions": {
- "version": "2.2.0",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/bintrees": {
- "version": "1.0.1"
- },
- "node_modules/body-parser": {
- "version": "1.19.2",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz",
- "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==",
- "dependencies": {
- "bytes": "3.1.2",
- "content-type": "~1.0.4",
- "debug": "2.6.9",
- "depd": "~1.1.2",
- "http-errors": "1.8.1",
- "iconv-lite": "0.4.24",
- "on-finished": "~2.3.0",
- "qs": "6.9.7",
- "raw-body": "2.4.3",
- "type-is": "~1.6.18"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/boolbase": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
- "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
- },
- "node_modules/bowser": {
- "version": "2.11.0",
- "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz",
- "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==",
- "optional": true,
- "peer": true
- },
- "node_modules/brace-expansion": {
- "version": "1.1.11",
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/braces": {
- "version": "3.0.2",
- "license": "MIT",
- "dependencies": {
- "fill-range": "^7.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/bson": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/bson/-/bson-6.3.0.tgz",
- "integrity": "sha512-balJfqwwTBddxfnidJZagCBPP/f48zj9Sdp3OJswREOgsJzHiQSaOIAtApSgDQFYgHqAvFkp53AFSqjMDZoTFw==",
- "engines": {
- "node": ">=16.20.1"
- }
- },
- "node_modules/buffer-crc32": {
- "version": "0.2.13",
- "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
- "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
- "dev": true,
- "engines": {
- "node": "*"
- }
- },
- "node_modules/buffer-equal-constant-time": {
- "version": "1.0.1",
- "license": "BSD-3-Clause"
- },
- "node_modules/buffer-from": {
- "version": "1.1.2",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/bullmq": {
- "version": "1.91.1",
- "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-1.91.1.tgz",
- "integrity": "sha512-u7dat9I8ZwouZ651AMZkBSvB6NVUPpnAjd4iokd9DM41whqIBnDjuL11h7+kEjcpiDKj6E+wxZiER00FqirZQg==",
- "dependencies": {
- "cron-parser": "^4.6.0",
- "get-port": "6.1.2",
- "glob": "^8.0.3",
- "ioredis": "^5.2.2",
- "lodash": "^4.17.21",
- "msgpackr": "^1.6.2",
- "semver": "^7.3.7",
- "tslib": "^2.0.0",
- "uuid": "^9.0.0"
- }
- },
- "node_modules/bullmq/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/bullmq/node_modules/debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/bullmq/node_modules/get-port": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/get-port/-/get-port-6.1.2.tgz",
- "integrity": "sha512-BrGGraKm2uPqurfGVj/z97/zv8dPleC6x9JBNRTrDNtCkkRF4rPwrQXFgL7+I+q8QSdU4ntLQX2D7KIxSy8nGw==",
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/bullmq/node_modules/glob": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
- "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^5.0.1",
- "once": "^1.3.0"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/bullmq/node_modules/ioredis": {
- "version": "5.3.2",
- "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz",
- "integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==",
- "dependencies": {
- "@ioredis/commands": "^1.1.1",
- "cluster-key-slot": "^1.1.0",
- "debug": "^4.3.4",
- "denque": "^2.1.0",
- "lodash.defaults": "^4.2.0",
- "lodash.isarguments": "^3.1.0",
- "redis-errors": "^1.2.0",
- "redis-parser": "^3.0.0",
- "standard-as-callback": "^2.1.0"
- },
- "engines": {
- "node": ">=12.22.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/ioredis"
- }
- },
- "node_modules/bullmq/node_modules/minimatch": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
- "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/bullmq/node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- },
- "node_modules/bytes": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
- "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/cac": {
- "version": "6.7.14",
- "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
- "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/call-bind": {
- "version": "1.0.2",
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.1",
- "get-intrinsic": "^1.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/camel-case": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
- "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==",
- "dependencies": {
- "no-case": "^2.2.0",
- "upper-case": "^1.1.1"
- }
- },
- "node_modules/camelcase": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
- "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
- "dev": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/chai": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz",
- "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==",
- "dev": true,
- "dependencies": {
- "assertion-error": "^1.1.0",
- "check-error": "^1.0.3",
- "deep-eql": "^4.1.3",
- "get-func-name": "^2.0.2",
- "loupe": "^2.3.6",
- "pathval": "^1.1.1",
- "type-detect": "^4.0.8"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/chalk": {
- "version": "4.1.2",
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
- },
- "node_modules/chalk/node_modules/has-flag": {
- "version": "4.0.0",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/chalk/node_modules/supports-color": {
- "version": "7.2.0",
- "license": "MIT",
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/check-error": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz",
- "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==",
- "dev": true,
- "dependencies": {
- "get-func-name": "^2.0.2"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/cheerio": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
- "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==",
- "dependencies": {
- "cheerio-select": "^2.1.0",
- "dom-serializer": "^2.0.0",
- "domhandler": "^5.0.3",
- "domutils": "^3.0.1",
- "htmlparser2": "^8.0.1",
- "parse5": "^7.0.0",
- "parse5-htmlparser2-tree-adapter": "^7.0.0"
- },
- "engines": {
- "node": ">= 6"
- },
- "funding": {
- "url": "https://github.com/cheeriojs/cheerio?sponsor=1"
- }
- },
- "node_modules/cheerio-select": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
- "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
- "dependencies": {
- "boolbase": "^1.0.0",
- "css-select": "^5.1.0",
- "css-what": "^6.1.0",
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.3",
- "domutils": "^3.0.1"
- },
- "funding": {
- "url": "https://github.com/sponsors/fb55"
- }
- },
- "node_modules/chokidar": {
- "version": "3.5.3",
- "funding": [
- {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "anymatch": "~3.1.2",
- "braces": "~3.0.2",
- "glob-parent": "~5.1.2",
- "is-binary-path": "~2.1.0",
- "is-glob": "~4.0.1",
- "normalize-path": "~3.0.0",
- "readdirp": "~3.6.0"
- },
- "engines": {
- "node": ">= 8.10.0"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/chownr": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
- "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/clean-css": {
- "version": "4.2.4",
- "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz",
- "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==",
- "dependencies": {
- "source-map": "~0.6.0"
- },
- "engines": {
- "node": ">= 4.0"
- }
- },
- "node_modules/cliui": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
- "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
- "dependencies": {
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.1",
- "wrap-ansi": "^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/cluster-key-slot": {
- "version": "1.1.0",
- "license": "APACHE-2.0",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/color": {
- "version": "3.2.1",
- "license": "MIT",
- "dependencies": {
- "color-convert": "^1.9.3",
- "color-string": "^1.6.0"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "license": "MIT"
- },
- "node_modules/color-string": {
- "version": "1.9.0",
- "license": "MIT",
- "dependencies": {
- "color-name": "^1.0.0",
- "simple-swizzle": "^0.2.2"
- }
- },
- "node_modules/color-support": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
- "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
- "bin": {
- "color-support": "bin.js"
- }
- },
- "node_modules/color/node_modules/color-convert": {
- "version": "1.9.3",
- "license": "MIT",
- "dependencies": {
- "color-name": "1.1.3"
- }
- },
- "node_modules/color/node_modules/color-name": {
- "version": "1.1.3",
- "license": "MIT"
- },
- "node_modules/colorspace": {
- "version": "1.1.4",
- "license": "MIT",
- "dependencies": {
- "color": "^3.1.3",
- "text-hex": "1.0.x"
- }
- },
- "node_modules/combined-stream": {
- "version": "1.0.8",
- "license": "MIT",
- "dependencies": {
- "delayed-stream": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/commander": {
- "version": "2.20.3",
- "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
- "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
- },
- "node_modules/commondir": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
- "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
- "dev": true
- },
- "node_modules/component-emitter": {
- "version": "1.3.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/compressible": {
- "version": "2.0.18",
- "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
- "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
- "optional": true,
- "dependencies": {
- "mime-db": ">= 1.43.0 < 2"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/concat-map": {
- "version": "0.0.1",
- "license": "MIT"
- },
- "node_modules/config-chain": {
- "version": "1.1.13",
- "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
- "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
- "dependencies": {
- "ini": "^1.3.4",
- "proto-list": "~1.2.1"
- }
- },
- "node_modules/console-control-strings": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
- "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
- },
- "node_modules/content-disposition": {
- "version": "0.5.4",
- "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
- "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
- "dependencies": {
- "safe-buffer": "5.2.1"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/content-disposition/node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ]
- },
- "node_modules/content-type": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
- "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/cookie": {
- "version": "0.4.2",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
- "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/cookie-signature": {
- "version": "1.0.6",
- "license": "MIT"
- },
- "node_modules/cookiejar": {
- "version": "2.1.3",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/cookies": {
- "version": "0.8.0",
- "license": "MIT",
- "dependencies": {
- "depd": "~2.0.0",
- "keygrip": "~1.1.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/cookies/node_modules/depd": {
- "version": "2.0.0",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/cors": {
- "version": "2.8.5",
- "license": "MIT",
- "dependencies": {
- "object-assign": "^4",
- "vary": "^1"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/create-require": {
- "version": "1.1.1",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/cron": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/cron/-/cron-2.3.0.tgz",
- "integrity": "sha512-ZN5HP8zDY41sJolMsbc+GksRATcbvkPKF5wR/qc8FrV4NBVi9ORQa1HmYa5GydaysUB80X9XpRlRkooa5uEtTA==",
- "dependencies": {
- "luxon": "^3.2.1"
- }
- },
- "node_modules/cron-parser": {
- "version": "4.8.1",
- "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.8.1.tgz",
- "integrity": "sha512-jbokKWGcyU4gl6jAfX97E1gDpY12DJ1cLJZmoDzaAln/shZ+S3KBFBuA2Q6WeUN4gJf/8klnV1EfvhA2lK5IRQ==",
- "dependencies": {
- "luxon": "^3.2.1"
- },
- "engines": {
- "node": ">=12.0.0"
- }
- },
- "node_modules/cross-spawn": {
- "version": "7.0.3",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
- "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
- "dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/css-select": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
- "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
- "dependencies": {
- "boolbase": "^1.0.0",
- "css-what": "^6.1.0",
- "domhandler": "^5.0.2",
- "domutils": "^3.0.1",
- "nth-check": "^2.0.1"
- },
- "funding": {
- "url": "https://github.com/sponsors/fb55"
- }
- },
- "node_modules/css-what": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
- "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
- "engines": {
- "node": ">= 6"
- },
- "funding": {
- "url": "https://github.com/sponsors/fb55"
- }
- },
- "node_modules/date-fns": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
- "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/kossnocorp"
- }
- },
- "node_modules/debug": {
- "version": "2.6.9",
- "license": "MIT",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/deep-eql": {
- "version": "4.1.3",
- "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz",
- "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==",
- "dev": true,
- "dependencies": {
- "type-detect": "^4.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/deepmerge": {
- "version": "4.2.2",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/delayed-stream": {
- "version": "1.0.0",
- "license": "MIT",
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/delegates": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
- "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
- },
- "node_modules/denque": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
- "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/depd": {
- "version": "1.1.2",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/destroy": {
- "version": "1.0.4",
- "license": "MIT"
- },
- "node_modules/detect-libc": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
- "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/detect-node": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
- "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="
- },
- "node_modules/dezalgo": {
- "version": "1.0.3",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "asap": "^2.0.0",
- "wrappy": "1"
- }
- },
- "node_modules/diff": {
- "version": "4.0.2",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.3.1"
- }
- },
- "node_modules/diff-sequences": {
- "version": "29.6.3",
- "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
- "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
- "dev": true,
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/dom-serializer": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
- "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
- "dependencies": {
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.2",
- "entities": "^4.2.0"
- },
- "funding": {
- "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
- }
- },
- "node_modules/domelementtype": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
- "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/fb55"
- }
- ]
- },
- "node_modules/domhandler": {
- "version": "5.0.3",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
- "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
- "dependencies": {
- "domelementtype": "^2.3.0"
- },
- "engines": {
- "node": ">= 4"
- },
- "funding": {
- "url": "https://github.com/fb55/domhandler?sponsor=1"
- }
- },
- "node_modules/domutils": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
- "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
- "dependencies": {
- "dom-serializer": "^2.0.0",
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.3"
- },
- "funding": {
- "url": "https://github.com/fb55/domutils?sponsor=1"
- }
- },
- "node_modules/dotenv": {
- "version": "10.0.0",
- "license": "BSD-2-Clause",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/duplexify": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz",
- "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==",
- "optional": true,
- "dependencies": {
- "end-of-stream": "^1.4.1",
- "inherits": "^2.0.3",
- "readable-stream": "^3.1.1",
- "stream-shift": "^1.0.0"
- }
- },
- "node_modules/dynamic-dedupe": {
- "version": "0.3.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "xtend": "^4.0.0"
- }
- },
- "node_modules/eastasianwidth": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
- },
- "node_modules/ecdsa-sig-formatter": {
- "version": "1.0.11",
- "license": "Apache-2.0",
- "dependencies": {
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/editorconfig": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz",
- "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==",
- "dependencies": {
- "@one-ini/wasm": "0.1.1",
- "commander": "^10.0.0",
- "minimatch": "9.0.1",
- "semver": "^7.5.3"
- },
- "bin": {
- "editorconfig": "bin/editorconfig"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/editorconfig/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/editorconfig/node_modules/commander": {
- "version": "10.0.1",
- "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
- "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/editorconfig/node_modules/minimatch": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz",
- "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/ee-first": {
- "version": "1.1.1",
- "license": "MIT"
- },
- "node_modules/emoji-regex": {
- "version": "8.0.0",
- "license": "MIT"
- },
- "node_modules/enabled": {
- "version": "2.0.0",
- "license": "MIT"
- },
- "node_modules/encodeurl": {
- "version": "1.0.2",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/end-of-stream": {
- "version": "1.4.4",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "once": "^1.4.0"
- }
- },
- "node_modules/ent": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz",
- "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==",
- "optional": true
- },
- "node_modules/entities": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
- "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
- "engines": {
- "node": ">=0.12"
- },
- "funding": {
- "url": "https://github.com/fb55/entities?sponsor=1"
- }
- },
- "node_modules/esbuild": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz",
- "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==",
- "dev": true,
- "hasInstallScript": true,
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=12"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.20.2",
- "@esbuild/android-arm": "0.20.2",
- "@esbuild/android-arm64": "0.20.2",
- "@esbuild/android-x64": "0.20.2",
- "@esbuild/darwin-arm64": "0.20.2",
- "@esbuild/darwin-x64": "0.20.2",
- "@esbuild/freebsd-arm64": "0.20.2",
- "@esbuild/freebsd-x64": "0.20.2",
- "@esbuild/linux-arm": "0.20.2",
- "@esbuild/linux-arm64": "0.20.2",
- "@esbuild/linux-ia32": "0.20.2",
- "@esbuild/linux-loong64": "0.20.2",
- "@esbuild/linux-mips64el": "0.20.2",
- "@esbuild/linux-ppc64": "0.20.2",
- "@esbuild/linux-riscv64": "0.20.2",
- "@esbuild/linux-s390x": "0.20.2",
- "@esbuild/linux-x64": "0.20.2",
- "@esbuild/netbsd-x64": "0.20.2",
- "@esbuild/openbsd-x64": "0.20.2",
- "@esbuild/sunos-x64": "0.20.2",
- "@esbuild/win32-arm64": "0.20.2",
- "@esbuild/win32-ia32": "0.20.2",
- "@esbuild/win32-x64": "0.20.2"
- }
- },
- "node_modules/escalade": {
- "version": "3.1.1",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/escape-goat": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-3.0.0.tgz",
- "integrity": "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw==",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/escape-html": {
- "version": "1.0.3",
- "license": "MIT"
- },
- "node_modules/estree-walker": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
- "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
- "dev": true,
- "dependencies": {
- "@types/estree": "^1.0.0"
- }
- },
- "node_modules/etag": {
- "version": "1.8.1",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/event-target-shim": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
- "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
- "optional": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/express": {
- "version": "4.17.3",
- "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz",
- "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==",
- "dependencies": {
- "accepts": "~1.3.8",
- "array-flatten": "1.1.1",
- "body-parser": "1.19.2",
- "content-disposition": "0.5.4",
- "content-type": "~1.0.4",
- "cookie": "0.4.2",
- "cookie-signature": "1.0.6",
- "debug": "2.6.9",
- "depd": "~1.1.2",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "finalhandler": "~1.1.2",
- "fresh": "0.5.2",
- "merge-descriptors": "1.0.1",
- "methods": "~1.1.2",
- "on-finished": "~2.3.0",
- "parseurl": "~1.3.3",
- "path-to-regexp": "0.1.7",
- "proxy-addr": "~2.0.7",
- "qs": "6.9.7",
- "range-parser": "~1.2.1",
- "safe-buffer": "5.2.1",
- "send": "0.17.2",
- "serve-static": "1.14.2",
- "setprototypeof": "1.2.0",
- "statuses": "~1.5.0",
- "type-is": "~1.6.18",
- "utils-merge": "1.0.1",
- "vary": "~1.1.2"
- },
- "engines": {
- "node": ">= 0.10.0"
- }
- },
- "node_modules/express-rate-limit": {
- "version": "6.2.1",
- "license": "MIT",
- "engines": {
- "node": ">= 14.5.0"
- },
- "peerDependencies": {
- "express": "^4"
- }
- },
- "node_modules/express/node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ]
- },
- "node_modules/extend": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
- "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
- "optional": true
- },
- "node_modules/fast-decode-uri-component": {
- "version": "1.0.1",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "devOptional": true,
- "license": "MIT"
- },
- "node_modules/fast-fifo": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
- "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
- "dev": true
- },
- "node_modules/fast-json-stable-stringify": {
- "version": "2.1.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-json-stringify": {
- "version": "2.7.13",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ajv": "^6.11.0",
- "deepmerge": "^4.2.2",
- "rfdc": "^1.2.0",
- "string-similarity": "^4.0.1"
- },
- "engines": {
- "node": ">= 10.0.0"
- }
- },
- "node_modules/fast-redact": {
- "version": "3.1.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/fast-safe-stringify": {
- "version": "2.1.1",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fast-xml-parser": {
- "version": "4.2.5",
- "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz",
- "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==",
- "funding": [
- {
- "type": "paypal",
- "url": "https://paypal.me/naturalintelligence"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/NaturalIntelligence"
- }
- ],
- "optional": true,
- "peer": true,
- "dependencies": {
- "strnum": "^1.0.5"
- },
- "bin": {
- "fxparser": "src/cli/cli.js"
- }
- },
- "node_modules/fastify": {
- "version": "3.27.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@fastify/ajv-compiler": "^1.0.0",
- "abstract-logging": "^2.0.0",
- "avvio": "^7.1.2",
- "fast-json-stringify": "^2.5.2",
- "fastify-error": "^0.3.0",
- "find-my-way": "^4.5.0",
- "flatstr": "^1.0.12",
- "light-my-request": "^4.2.0",
- "pino": "^6.13.0",
- "process-warning": "^1.0.0",
- "proxy-addr": "^2.0.7",
- "rfdc": "^1.1.4",
- "secure-json-parse": "^2.0.0",
- "semver": "^7.3.2",
- "tiny-lru": "^8.0.1"
- }
- },
- "node_modules/fastify-error": {
- "version": "0.3.1",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fastq": {
- "version": "1.13.0",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "reusify": "^1.0.4"
- }
- },
- "node_modules/faye-websocket": {
- "version": "0.11.4",
- "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
- "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
- "dependencies": {
- "websocket-driver": ">=0.5.1"
- },
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/fecha": {
- "version": "4.2.1",
- "license": "MIT"
- },
- "node_modules/fengari": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/fengari/-/fengari-0.1.4.tgz",
- "integrity": "sha512-6ujqUuiIYmcgkGz8MGAdERU57EIluGGPSUgGPTsco657EHa+srq0S3/YUl/r9kx1+D+d4rGfYObd+m8K22gB1g==",
- "dev": true,
- "dependencies": {
- "readline-sync": "^1.4.9",
- "sprintf-js": "^1.1.1",
- "tmp": "^0.0.33"
- }
- },
- "node_modules/fengari-interop": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/fengari-interop/-/fengari-interop-0.1.3.tgz",
- "integrity": "sha512-EtZ+oTu3kEwVJnoymFPBVLIbQcCoy9uWCVnMA6h3M/RqHkUBsLYp29+RRHf9rKr6GwjubWREU1O7RretFIXjHw==",
- "dev": true,
- "peerDependencies": {
- "fengari": "^0.1.0"
- }
- },
- "node_modules/fengari/node_modules/sprintf-js": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
- "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
- "dev": true
- },
- "node_modules/fengari/node_modules/tmp": {
- "version": "0.0.33",
- "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
- "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
- "dev": true,
- "dependencies": {
- "os-tmpdir": "~1.0.2"
- },
- "engines": {
- "node": ">=0.6.0"
- }
- },
- "node_modules/fill-range": {
- "version": "7.0.1",
- "license": "MIT",
- "dependencies": {
- "to-regex-range": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/finalhandler": {
- "version": "1.1.2",
- "license": "MIT",
- "dependencies": {
- "debug": "2.6.9",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "on-finished": "~2.3.0",
- "parseurl": "~1.3.3",
- "statuses": "~1.5.0",
- "unpipe": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/find-cache-dir": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
- "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
- "dev": true,
- "dependencies": {
- "commondir": "^1.0.1",
- "make-dir": "^3.0.2",
- "pkg-dir": "^4.1.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/avajs/find-cache-dir?sponsor=1"
- }
- },
- "node_modules/find-my-way": {
- "version": "4.5.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-decode-uri-component": "^1.0.1",
- "fast-deep-equal": "^3.1.3",
- "safe-regex2": "^2.0.0",
- "semver-store": "^0.3.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/find-up": {
- "version": "4.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "locate-path": "^5.0.0",
- "path-exists": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/firebase-admin": {
- "version": "12.0.0",
- "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-12.0.0.tgz",
- "integrity": "sha512-wBrrSSsKV++/+O8E7O/C7/wL0nbG/x4Xv4yatz/+sohaZ+LsnWtYUcrd3gZutO86hLpDex7xgyrkKbgulmtVyQ==",
- "dependencies": {
- "@fastify/busboy": "^1.2.1",
- "@firebase/database-compat": "^1.0.2",
- "@firebase/database-types": "^1.0.0",
- "@types/node": "^20.10.3",
- "jsonwebtoken": "^9.0.0",
- "jwks-rsa": "^3.0.1",
- "node-forge": "^1.3.1",
- "uuid": "^9.0.0"
- },
- "engines": {
- "node": ">=14"
- },
- "optionalDependencies": {
- "@google-cloud/firestore": "^7.1.0",
- "@google-cloud/storage": "^7.7.0"
- }
- },
- "node_modules/firebase-admin/node_modules/@types/node": {
- "version": "20.11.17",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.17.tgz",
- "integrity": "sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==",
- "dependencies": {
- "undici-types": "~5.26.4"
- }
- },
- "node_modules/flatstr": {
- "version": "1.0.12",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/fn.name": {
- "version": "1.1.0",
- "license": "MIT"
- },
- "node_modules/follow-redirects": {
- "version": "1.15.6",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
- "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/RubenVerborgh"
- }
- ],
- "engines": {
- "node": ">=4.0"
- },
- "peerDependenciesMeta": {
- "debug": {
- "optional": true
- }
- }
- },
- "node_modules/foreground-child": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
- "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
- "dependencies": {
- "cross-spawn": "^7.0.0",
- "signal-exit": "^4.0.1"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/foreground-child/node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/form-data": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
- "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==",
- "optional": true,
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.6",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 0.12"
- }
- },
- "node_modules/formidable": {
- "version": "2.0.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "dezalgo": "1.0.3",
- "hexoid": "1.0.0",
- "once": "1.4.0",
- "qs": "6.9.3"
- },
- "funding": {
- "url": "https://ko-fi.com/tunnckoCore/commissions"
- }
- },
- "node_modules/formidable/node_modules/qs": {
- "version": "6.9.3",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/forwarded": {
- "version": "0.2.0",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/fresh": {
- "version": "0.5.2",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/fs-minipass": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
- "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
- "dependencies": {
- "minipass": "^3.0.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/fs-minipass/node_modules/minipass": {
- "version": "3.3.6",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
- "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
- "dependencies": {
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/fs.realpath": {
- "version": "1.0.0",
- "license": "ISC"
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/function-bind": {
- "version": "1.1.1",
- "license": "MIT"
- },
- "node_modules/functional-red-black-tree": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
- "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==",
- "optional": true
- },
- "node_modules/gauge": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
- "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
- "dependencies": {
- "aproba": "^1.0.3 || ^2.0.0",
- "color-support": "^1.1.2",
- "console-control-strings": "^1.0.0",
- "has-unicode": "^2.0.1",
- "object-assign": "^4.1.1",
- "signal-exit": "^3.0.0",
- "string-width": "^4.2.3",
- "strip-ansi": "^6.0.1",
- "wide-align": "^1.1.2"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/gaxios": {
- "version": "6.2.0",
- "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.2.0.tgz",
- "integrity": "sha512-H6+bHeoEAU5D6XNc6mPKeN5dLZqEDs9Gpk6I+SZBEzK5So58JVrHPmevNi35fRl1J9Y5TaeLW0kYx3pCJ1U2mQ==",
- "optional": true,
- "dependencies": {
- "extend": "^3.0.2",
- "https-proxy-agent": "^7.0.1",
- "is-stream": "^2.0.0",
- "node-fetch": "^2.6.9"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/gaxios/node_modules/agent-base": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
- "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
- "optional": true,
- "dependencies": {
- "debug": "^4.3.4"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/gaxios/node_modules/debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "optional": true,
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/gaxios/node_modules/https-proxy-agent": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz",
- "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==",
- "optional": true,
- "dependencies": {
- "agent-base": "^7.0.2",
- "debug": "4"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/gaxios/node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "optional": true
- },
- "node_modules/gaxios/node_modules/node-fetch": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
- "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
- "optional": true,
- "dependencies": {
- "whatwg-url": "^5.0.0"
- },
- "engines": {
- "node": "4.x || >=6.0.0"
- },
- "peerDependencies": {
- "encoding": "^0.1.0"
- },
- "peerDependenciesMeta": {
- "encoding": {
- "optional": true
- }
- }
- },
- "node_modules/gcp-metadata": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz",
- "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "gaxios": "^5.0.0",
- "json-bigint": "^1.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/gcp-metadata/node_modules/gaxios": {
- "version": "5.1.3",
- "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz",
- "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "extend": "^3.0.2",
- "https-proxy-agent": "^5.0.0",
- "is-stream": "^2.0.0",
- "node-fetch": "^2.6.9"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/gcp-metadata/node_modules/node-fetch": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
- "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
- "optional": true,
- "peer": true,
- "dependencies": {
- "whatwg-url": "^5.0.0"
- },
- "engines": {
- "node": "4.x || >=6.0.0"
- },
- "peerDependencies": {
- "encoding": "^0.1.0"
- },
- "peerDependenciesMeta": {
- "encoding": {
- "optional": true
- }
- }
- },
- "node_modules/get-caller-file": {
- "version": "2.0.5",
- "license": "ISC",
- "engines": {
- "node": "6.* || 8.* || >= 10.*"
- }
- },
- "node_modules/get-func-name": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz",
- "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==",
- "dev": true,
- "engines": {
- "node": "*"
- }
- },
- "node_modules/get-intrinsic": {
- "version": "1.1.1",
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.1",
- "has": "^1.0.3",
- "has-symbols": "^1.0.1"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/glob": {
- "version": "7.2.0",
- "license": "ISC",
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/glob-parent": {
- "version": "5.1.2",
- "license": "ISC",
- "dependencies": {
- "is-glob": "^4.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/google-auth-library": {
- "version": "9.6.3",
- "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.6.3.tgz",
- "integrity": "sha512-4CacM29MLC2eT9Cey5GDVK4Q8t+MMp8+OEdOaqD9MG6b0dOyLORaaeJMPQ7EESVgm/+z5EKYyFLxgzBJlJgyHQ==",
- "optional": true,
- "dependencies": {
- "base64-js": "^1.3.0",
- "ecdsa-sig-formatter": "^1.0.11",
- "gaxios": "^6.1.1",
- "gcp-metadata": "^6.1.0",
- "gtoken": "^7.0.0",
- "jws": "^4.0.0"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/google-auth-library/node_modules/gcp-metadata": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz",
- "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==",
- "optional": true,
- "dependencies": {
- "gaxios": "^6.0.0",
- "json-bigint": "^1.0.0"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/google-gax": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.3.1.tgz",
- "integrity": "sha512-qpSfslpwqToIgQ+Tf3MjWIDjYK4UFIZ0uz6nLtttlW9N1NQA4PhGf9tlGo6KDYJ4rgL2w4CjXVd0z5yeNpN/Iw==",
- "optional": true,
- "dependencies": {
- "@grpc/grpc-js": "~1.10.0",
- "@grpc/proto-loader": "^0.7.0",
- "@types/long": "^4.0.0",
- "abort-controller": "^3.0.0",
- "duplexify": "^4.0.0",
- "google-auth-library": "^9.3.0",
- "node-fetch": "^2.6.1",
- "object-hash": "^3.0.0",
- "proto3-json-serializer": "^2.0.0",
- "protobufjs": "7.2.6",
- "retry-request": "^7.0.0",
- "uuid": "^9.0.1"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/gtoken": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
- "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==",
- "optional": true,
- "dependencies": {
- "gaxios": "^6.0.0",
- "jws": "^4.0.0"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/has": {
- "version": "1.0.3",
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4.0"
- }
- },
- "node_modules/has-flag": {
- "version": "3.0.0",
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/has-symbols": {
- "version": "1.0.2",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-unicode": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
- "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
- },
- "node_modules/he": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
- "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
- "bin": {
- "he": "bin/he"
- }
- },
- "node_modules/helmet": {
- "version": "4.6.0",
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/hexoid": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/html-escaper": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
- "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
- "dev": true
- },
- "node_modules/html-minifier": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz",
- "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==",
- "dependencies": {
- "camel-case": "^3.0.0",
- "clean-css": "^4.2.1",
- "commander": "^2.19.0",
- "he": "^1.2.0",
- "param-case": "^2.1.1",
- "relateurl": "^0.2.7",
- "uglify-js": "^3.5.1"
- },
- "bin": {
- "html-minifier": "cli.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/htmlparser2": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
- "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
- "funding": [
- "https://github.com/fb55/htmlparser2?sponsor=1",
- {
- "type": "github",
- "url": "https://github.com/sponsors/fb55"
- }
- ],
- "dependencies": {
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.3",
- "domutils": "^3.0.1",
- "entities": "^4.4.0"
- }
- },
- "node_modules/http-errors": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
- "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
- "dependencies": {
- "depd": "~1.1.2",
- "inherits": "2.0.4",
- "setprototypeof": "1.2.0",
- "statuses": ">= 1.5.0 < 2",
- "toidentifier": "1.0.1"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/http-errors/node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
- },
- "node_modules/http-parser-js": {
- "version": "0.5.8",
- "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz",
- "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q=="
- },
- "node_modules/http-proxy-agent": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
- "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
- "optional": true,
- "dependencies": {
- "@tootallnate/once": "2",
- "agent-base": "6",
- "debug": "4"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/http-proxy-agent/node_modules/debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "optional": true,
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/http-proxy-agent/node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "optional": true
- },
- "node_modules/https-proxy-agent": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
- "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
- "dependencies": {
- "agent-base": "6",
- "debug": "4"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/https-proxy-agent/node_modules/debug": {
- "version": "4.3.3",
- "license": "MIT",
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/https-proxy-agent/node_modules/ms": {
- "version": "2.1.2",
- "license": "MIT"
- },
- "node_modules/iconv-lite": {
- "version": "0.4.24",
- "license": "MIT",
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/ignore-by-default": {
- "version": "1.0.1",
- "license": "ISC"
- },
- "node_modules/inflight": {
- "version": "1.0.6",
- "license": "ISC",
- "dependencies": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "node_modules/inherits": {
- "version": "2.0.3",
- "license": "ISC"
- },
- "node_modules/ini": {
- "version": "1.3.8",
- "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
- "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="
- },
- "node_modules/ioredis": {
- "version": "4.28.5",
- "license": "MIT",
- "dependencies": {
- "cluster-key-slot": "^1.1.0",
- "debug": "^4.3.1",
- "denque": "^1.1.0",
- "lodash.defaults": "^4.2.0",
- "lodash.flatten": "^4.4.0",
- "lodash.isarguments": "^3.1.0",
- "p-map": "^2.1.0",
- "redis-commands": "1.7.0",
- "redis-errors": "^1.2.0",
- "redis-parser": "^3.0.0",
- "standard-as-callback": "^2.1.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/ioredis"
- }
- },
- "node_modules/ioredis-mock": {
- "version": "7.4.0",
- "resolved": "https://registry.npmjs.org/ioredis-mock/-/ioredis-mock-7.4.0.tgz",
- "integrity": "sha512-jcNG+9YjjBA1p6Hb1nYaC1yhW+n9S5VOgbGZXt59ZmtI2WrPWH+lSD4gE017uaGitPqW7tquFdAfcBPvFEQbew==",
- "dev": true,
- "dependencies": {
- "fengari": "^0.1.4",
- "fengari-interop": "^0.1.3",
- "redis-commands": "^1.7.0",
- "standard-as-callback": "^2.1.0"
- },
- "engines": {
- "node": ">=12"
- },
- "peerDependencies": {
- "ioredis": "4.x || 5.x"
- }
- },
- "node_modules/ioredis/node_modules/debug": {
- "version": "4.3.4",
- "license": "MIT",
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/ioredis/node_modules/denque": {
- "version": "1.5.1",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/ioredis/node_modules/ms": {
- "version": "2.1.2",
- "license": "MIT"
- },
- "node_modules/ip": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
- "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==",
- "devOptional": true
- },
- "node_modules/ipaddr.js": {
- "version": "1.9.1",
- "license": "MIT",
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/is-arrayish": {
- "version": "0.3.2",
- "license": "MIT"
- },
- "node_modules/is-binary-path": {
- "version": "2.1.0",
- "license": "MIT",
- "dependencies": {
- "binary-extensions": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-core-module": {
- "version": "2.9.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has": "^1.0.3"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-extglob": {
- "version": "2.1.1",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/is-glob": {
- "version": "4.0.3",
- "license": "MIT",
- "dependencies": {
- "is-extglob": "^2.1.1"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-number": {
- "version": "7.0.0",
- "license": "MIT",
- "engines": {
- "node": ">=0.12.0"
- }
- },
- "node_modules/is-stream": {
- "version": "2.0.1",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
- },
- "node_modules/istanbul-lib-coverage": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
- "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/istanbul-lib-report": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
- "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
- "dev": true,
- "dependencies": {
- "istanbul-lib-coverage": "^3.0.0",
- "make-dir": "^4.0.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/istanbul-lib-report/node_modules/has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/istanbul-lib-report/node_modules/make-dir": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
- "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
- "dev": true,
- "dependencies": {
- "semver": "^7.5.3"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/istanbul-lib-report/node_modules/supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "dependencies": {
- "has-flag": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/istanbul-lib-source-maps": {
- "version": "5.0.4",
- "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.4.tgz",
- "integrity": "sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==",
- "dev": true,
- "dependencies": {
- "@jridgewell/trace-mapping": "^0.3.23",
- "debug": "^4.1.1",
- "istanbul-lib-coverage": "^3.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/istanbul-lib-source-maps/node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
- "dev": true,
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/istanbul-lib-source-maps/node_modules/debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "dev": true,
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/istanbul-lib-source-maps/node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
- },
- "node_modules/istanbul-reports": {
- "version": "3.1.7",
- "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz",
- "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==",
- "dev": true,
- "dependencies": {
- "html-escaper": "^2.0.0",
- "istanbul-lib-report": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/jackspeak": {
- "version": "2.3.6",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
- "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
- "dependencies": {
- "@isaacs/cliui": "^8.0.2"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- },
- "optionalDependencies": {
- "@pkgjs/parseargs": "^0.11.0"
- }
- },
- "node_modules/joi": {
- "version": "17.6.0",
- "license": "BSD-3-Clause",
- "dependencies": {
- "@hapi/hoek": "^9.0.0",
- "@hapi/topo": "^5.0.0",
- "@sideway/address": "^4.1.3",
- "@sideway/formula": "^3.0.0",
- "@sideway/pinpoint": "^2.0.0"
- }
- },
- "node_modules/jose": {
- "version": "4.14.0",
- "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.0.tgz",
- "integrity": "sha512-LSA/XenLPwqk6e2L+PSUNuuY9G4NGsvjRWz6sJcUBmzTLEPJqQh46FHSUxnAQ64AWOkRO6bSXpy3yXuEKZkbIA==",
- "funding": {
- "url": "https://github.com/sponsors/panva"
- }
- },
- "node_modules/js-beautify": {
- "version": "1.14.11",
- "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.11.tgz",
- "integrity": "sha512-rPogWqAfoYh1Ryqqh2agUpVfbxAhbjuN1SmU86dskQUKouRiggUTCO4+2ym9UPXllc2WAp0J+T5qxn7Um3lCdw==",
- "dependencies": {
- "config-chain": "^1.1.13",
- "editorconfig": "^1.0.3",
- "glob": "^10.3.3",
- "nopt": "^7.2.0"
- },
- "bin": {
- "css-beautify": "js/bin/css-beautify.js",
- "html-beautify": "js/bin/html-beautify.js",
- "js-beautify": "js/bin/js-beautify.js"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/js-beautify/node_modules/abbrev": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
- "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==",
- "engines": {
- "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
- }
- },
- "node_modules/js-beautify/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/js-beautify/node_modules/glob": {
- "version": "10.3.10",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
- "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^2.3.5",
- "minimatch": "^9.0.1",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
- "path-scurry": "^1.10.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/js-beautify/node_modules/minimatch": {
- "version": "9.0.3",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
- "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/js-beautify/node_modules/minipass": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
- "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/js-beautify/node_modules/nopt": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz",
- "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==",
- "dependencies": {
- "abbrev": "^2.0.0"
- },
- "bin": {
- "nopt": "bin/nopt.js"
- },
- "engines": {
- "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
- }
- },
- "node_modules/json-bigint": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
- "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
- "optional": true,
- "dependencies": {
- "bignumber.js": "^9.0.0"
- }
- },
- "node_modules/json-schema-traverse": {
- "version": "0.4.1",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/jsonc-parser": {
- "version": "3.2.1",
- "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz",
- "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==",
- "dev": true
- },
- "node_modules/jsonwebtoken": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz",
- "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==",
- "dependencies": {
- "jws": "^3.2.2",
- "lodash": "^4.17.21",
- "ms": "^2.1.1",
- "semver": "^7.3.8"
- },
- "engines": {
- "node": ">=12",
- "npm": ">=6"
- }
- },
- "node_modules/jsonwebtoken/node_modules/jwa": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
- "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
- "dependencies": {
- "buffer-equal-constant-time": "1.0.1",
- "ecdsa-sig-formatter": "1.0.11",
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/jsonwebtoken/node_modules/jws": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
- "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
- "dependencies": {
- "jwa": "^1.4.1",
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/jsonwebtoken/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
- },
- "node_modules/juice": {
- "version": "10.0.0",
- "resolved": "https://registry.npmjs.org/juice/-/juice-10.0.0.tgz",
- "integrity": "sha512-9f68xmhGrnIi6DBkiiP3rUrQN33SEuaKu1+njX6VgMP+jwZAsnT33WIzlrWICL9matkhYu3OyrqSUP55YTIdGg==",
- "dependencies": {
- "cheerio": "^1.0.0-rc.12",
- "commander": "^6.1.0",
- "mensch": "^0.3.4",
- "slick": "^1.12.2",
- "web-resource-inliner": "^6.0.1"
- },
- "bin": {
- "juice": "bin/juice"
- },
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/juice/node_modules/commander": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz",
- "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/jwa": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
- "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
- "optional": true,
- "dependencies": {
- "buffer-equal-constant-time": "1.0.1",
- "ecdsa-sig-formatter": "1.0.11",
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/jwks-rsa": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.0.1.tgz",
- "integrity": "sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==",
- "dependencies": {
- "@types/express": "^4.17.14",
- "@types/jsonwebtoken": "^9.0.0",
- "debug": "^4.3.4",
- "jose": "^4.10.4",
- "limiter": "^1.1.5",
- "lru-memoizer": "^2.1.4"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/jwks-rsa/node_modules/debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/jwks-rsa/node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- },
- "node_modules/jws": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
- "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
- "optional": true,
- "dependencies": {
- "jwa": "^2.0.0",
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/keygrip": {
- "version": "1.1.0",
- "license": "MIT",
- "dependencies": {
- "tsscmp": "1.0.6"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/kuler": {
- "version": "2.0.0",
- "license": "MIT"
- },
- "node_modules/light-my-request": {
- "version": "4.8.0",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "ajv": "^8.1.0",
- "cookie": "^0.4.0",
- "process-warning": "^1.0.0",
- "set-cookie-parser": "^2.4.1"
- }
- },
- "node_modules/light-my-request/node_modules/ajv": {
- "version": "8.10.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "json-schema-traverse": "^1.0.0",
- "require-from-string": "^2.0.2",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/light-my-request/node_modules/json-schema-traverse": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/limiter": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
- "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA=="
- },
- "node_modules/local-pkg": {
- "version": "0.5.0",
- "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz",
- "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==",
- "dev": true,
- "dependencies": {
- "mlly": "^1.4.2",
- "pkg-types": "^1.0.3"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/antfu"
- }
- },
- "node_modules/locate-path": {
- "version": "5.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-locate": "^4.1.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/lodash": {
- "version": "4.17.21",
- "license": "MIT"
- },
- "node_modules/lodash.camelcase": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
- "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
- "optional": true
- },
- "node_modules/lodash.clonedeep": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
- "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
- },
- "node_modules/lodash.defaults": {
- "version": "4.2.0",
- "license": "MIT"
- },
- "node_modules/lodash.flatten": {
- "version": "4.4.0",
- "license": "MIT"
- },
- "node_modules/lodash.isarguments": {
- "version": "3.1.0",
- "license": "MIT"
- },
- "node_modules/logform": {
- "version": "2.4.0",
- "license": "MIT",
- "dependencies": {
- "@colors/colors": "1.5.0",
- "fecha": "^4.2.0",
- "ms": "^2.1.1",
- "safe-stable-stringify": "^2.3.1",
- "triple-beam": "^1.3.0"
- }
- },
- "node_modules/logform/node_modules/ms": {
- "version": "2.1.3",
- "license": "MIT"
- },
- "node_modules/long": {
- "version": "5.2.3",
- "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz",
- "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==",
- "optional": true
- },
- "node_modules/loupe": {
- "version": "2.3.7",
- "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz",
- "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==",
- "dev": true,
- "dependencies": {
- "get-func-name": "^2.0.1"
- }
- },
- "node_modules/lower-case": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
- "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA=="
- },
- "node_modules/lru-cache": {
- "version": "7.10.1",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.10.1.tgz",
- "integrity": "sha512-BQuhQxPuRl79J5zSXRP+uNzPOyZw2oFI9JLRQ80XswSvg21KMKNtQza9eF42rfI/3Z40RvzBdXgziEkudzjo8A==",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/lru-memoizer": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.2.0.tgz",
- "integrity": "sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw==",
- "dependencies": {
- "lodash.clonedeep": "^4.5.0",
- "lru-cache": "~4.0.0"
- }
- },
- "node_modules/lru-memoizer/node_modules/lru-cache": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz",
- "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==",
- "dependencies": {
- "pseudomap": "^1.0.1",
- "yallist": "^2.0.0"
- }
- },
- "node_modules/lru-memoizer/node_modules/yallist": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
- "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A=="
- },
- "node_modules/luxon": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz",
- "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/magic-string": {
- "version": "0.30.10",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
- "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
- "dev": true,
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.4.15"
- }
- },
- "node_modules/magicast": {
- "version": "0.3.4",
- "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz",
- "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==",
- "dev": true,
- "dependencies": {
- "@babel/parser": "^7.24.4",
- "@babel/types": "^7.24.0",
- "source-map-js": "^1.2.0"
- }
- },
- "node_modules/make-dir": {
- "version": "3.1.0",
- "license": "MIT",
- "dependencies": {
- "semver": "^6.0.0"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/make-dir/node_modules/semver": {
- "version": "6.3.0",
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- }
- },
- "node_modules/make-error": {
- "version": "1.3.6",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/media-typer": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
- "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/memory-pager": {
- "version": "1.5.0",
- "license": "MIT"
- },
- "node_modules/mensch": {
- "version": "0.3.4",
- "resolved": "https://registry.npmjs.org/mensch/-/mensch-0.3.4.tgz",
- "integrity": "sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g=="
- },
- "node_modules/merge-descriptors": {
- "version": "1.0.1",
- "license": "MIT"
- },
- "node_modules/merge-stream": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
- "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
- "dev": true
- },
- "node_modules/methods": {
- "version": "1.1.2",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime": {
- "version": "1.6.0",
- "license": "MIT",
- "bin": {
- "mime": "cli.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/mime-db": {
- "version": "1.51.0",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "2.1.34",
- "license": "MIT",
- "dependencies": {
- "mime-db": "1.51.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/minimist": {
- "version": "1.2.6",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/minipass": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
- "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/minizlib": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
- "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
- "dependencies": {
- "minipass": "^3.0.0",
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/minizlib/node_modules/minipass": {
- "version": "3.3.6",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
- "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
- "dependencies": {
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/mjml": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml/-/mjml-4.15.0.tgz",
- "integrity": "sha512-vAeqvq915Qgdn0sYxwldsVSIKNn2HAzUHXG7orgYTrMUD7Vfq3B1W5WYsa0oV/JRgLR6SH5MrFPsvckgc4mxxg==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "mjml-cli": "4.15.0",
- "mjml-core": "4.15.0",
- "mjml-migrate": "4.15.0",
- "mjml-preset-core": "4.15.0",
- "mjml-validator": "4.13.0"
- },
- "bin": {
- "mjml": "bin/mjml"
- }
- },
- "node_modules/mjml-accordion": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-accordion/-/mjml-accordion-4.15.0.tgz",
- "integrity": "sha512-SXhyfxylwF6tT8Ls9XUhbBc3tUIr5DHnhBLC44+hPgOXkogkjfkxVcX9j0Gel8umuHeFQ3+kGagW1NtiwW/v1w==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-body": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-body/-/mjml-body-4.15.0.tgz",
- "integrity": "sha512-Epf0zL8swIR4RQ/pQQdsIMIdbrez5aRPSGoKc/9Y18OVY9LZj8KqgAlTaCALKykxtwOQZ57ABJ41saJ58BaTbA==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-button": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-button/-/mjml-button-4.15.0.tgz",
- "integrity": "sha512-MSYW2cuIP4gk6Rf4nOq/+N1n0Bj6c+2kb584Sm0LhNEmQa6FLdD1u6JzydIG8NGCNkOEZw39U2626Wi6L3Nweg==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-carousel": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-carousel/-/mjml-carousel-4.15.0.tgz",
- "integrity": "sha512-22M0VQZ0RM22DZXWomRMe5vfUqBRcJuMsHrF5umjI0BeieoutEzgB2bT3ess0LYNFciRMArcYNs2CnkCRX3RzQ==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-cli": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-cli/-/mjml-cli-4.15.0.tgz",
- "integrity": "sha512-F/3TA8tXVUneBMR6JjS7+r8IlmXp7NjEbJRKcpJGzGjYmoZajbBuaOHkpmcU5IMT0KeRULSSe9HYJVH754jNwA==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "chokidar": "^3.0.0",
- "glob": "^10.3.10",
- "html-minifier": "^4.0.0",
- "js-beautify": "^1.6.14",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0",
- "mjml-migrate": "4.15.0",
- "mjml-parser-xml": "4.15.0",
- "mjml-validator": "4.13.0",
- "yargs": "^17.7.2"
- },
- "bin": {
- "mjml-cli": "bin/mjml"
- }
- },
- "node_modules/mjml-cli/node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/mjml-cli/node_modules/glob": {
- "version": "10.3.10",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
- "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^2.3.5",
- "minimatch": "^9.0.1",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
- "path-scurry": "^1.10.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/mjml-cli/node_modules/minimatch": {
- "version": "9.0.3",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
- "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/mjml-cli/node_modules/minipass": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
- "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/mjml-column": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-column/-/mjml-column-4.15.0.tgz",
- "integrity": "sha512-XatibYOclg34Orj7aF96mS7i6orBo87QvgraLr4wFimp2RyuXIjEgiTpXQMFx2M51WSM4SdGfs75MwibE5q4zg==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-core": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-core/-/mjml-core-4.15.0.tgz",
- "integrity": "sha512-ZYooSD+/F0zTl/lKF4s4MBTJ96/LoQ5zoodWBuuToXNbYQNAaxDWB2VwlXJYQn6sUiiYCLm3YZAjlpiTlBJpIQ==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "cheerio": "1.0.0-rc.12",
- "detect-node": "^2.0.4",
- "html-minifier": "^4.0.0",
- "js-beautify": "^1.6.14",
- "juice": "^10.0.0",
- "lodash": "^4.17.21",
- "mjml-migrate": "4.15.0",
- "mjml-parser-xml": "4.15.0",
- "mjml-validator": "4.13.0"
- }
- },
- "node_modules/mjml-divider": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-divider/-/mjml-divider-4.15.0.tgz",
- "integrity": "sha512-1N3HPntqZ3HUf2fVSTRfujYC2YSZXL9oJqIqthtvoKGSxBBQMrQXtHEsScb06Lz9eeHvzmpELqv2R1916nDFiw==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-group": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-group/-/mjml-group-4.15.0.tgz",
- "integrity": "sha512-p9okTFmQ67S3PX/1jyBsJ3G1LU5/Vacsg07SCPgKY1NEgAjEmHY+30/JTTTjxyvtccB0WdbaE1C+86faADepsQ==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-head": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-head/-/mjml-head-4.15.0.tgz",
- "integrity": "sha512-JYsyQ4sk4h+FniQsYn/R4wSXZVhVcRouFD48aCv/Wo9sxUsw+aYwdguQ4/a7IEY63jV+cow2a6rexC3cpxqCIg==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-head-attributes": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-head-attributes/-/mjml-head-attributes-4.15.0.tgz",
- "integrity": "sha512-344V+fRdps+Zaj4SF9Mi2LBm5AbcN1MutOr3J46jPARZByUjkn7JiEAeB8mHeL5usKCX+WRFvEoikyK+QODC+g==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-head-breakpoint": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-head-breakpoint/-/mjml-head-breakpoint-4.15.0.tgz",
- "integrity": "sha512-fXCVp3IrhMHSfubDjFlgxQq60nus7ztR7NV3WKrqkdoe+InqOozwZztOCZQ03KcVOtGIknlFkSVqgqb+S2UsTg==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-head-font": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-head-font/-/mjml-head-font-4.15.0.tgz",
- "integrity": "sha512-kLrO8GAT6x1xFaHEvweClmculUIwdfdfNYKFBU5vSjd6QTHcVKFmN+uCp9463fc1BesFlkE9+24tv4A+RcOe1Q==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-head-html-attributes": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-head-html-attributes/-/mjml-head-html-attributes-4.15.0.tgz",
- "integrity": "sha512-/1rBC2MiVqwxTmMxbFSE0zh5JcewvsHXbZLnOAl51fz2DEKuW9RnmJFgyYp36ikT+POPAxlOQxDbKUySaqRTJQ==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-head-preview": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-head-preview/-/mjml-head-preview-4.15.0.tgz",
- "integrity": "sha512-9uWSKUe2dIpKzPHQAYcSeiwoSuipQxFEKh/IC56gM4A3ROI8HJBbJV9cITT6RBxbj5+QY839ac3H82ddA0iEEw==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-head-style": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-head-style/-/mjml-head-style-4.15.0.tgz",
- "integrity": "sha512-HGSaTxZGcB0l79GsthJDo6kaaowz8dCAcXJIM+aSpuvY/GCtVxSu5kFjif90xdtN3o+Tye0qIojp+vupZPLqJA==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-head-title": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-head-title/-/mjml-head-title-4.15.0.tgz",
- "integrity": "sha512-2cQkc7BMQC7yPwhhO7wYFDH7xSzABvSh6d1jURWRgP2EBAAmznb3hVo4ssrGg7LNwy9MCvKWS+7utrxzbcu49g==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-hero": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-hero/-/mjml-hero-4.15.0.tgz",
- "integrity": "sha512-SLZ367lQx1VEVdhz7VA3IdotQHui/3nce9aRKogHVa9YTWs17HKNZ4AhFUTWUGBAB4NpkhWLTrRbCFpuegUllA==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-image": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-image/-/mjml-image-4.15.0.tgz",
- "integrity": "sha512-e9bSwjSQMVJX2l37RzQOrcTvn8ECGU+2zMxuQQDLegvbozcKX+u8Pw5EBtrENt7eNPexGqGa+Y5ityQM0s5kxw==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-migrate": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-migrate/-/mjml-migrate-4.15.0.tgz",
- "integrity": "sha512-G32xJyGu4vvGdS3DS5anakJsNtNsPU7nRFeB1EOxVXnVJ4Dqc4jB04UEYkDgM7lf28z52xWXJDeDJ/ewve21Fg==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "js-beautify": "^1.6.14",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0",
- "mjml-parser-xml": "4.15.0",
- "yargs": "^17.7.2"
- },
- "bin": {
- "migrate": "lib/cli.js"
- }
- },
- "node_modules/mjml-navbar": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-navbar/-/mjml-navbar-4.15.0.tgz",
- "integrity": "sha512-ZZyeYN1CPgAfnCHjTkkm5NvdTYjUeE6ct+Cy3Xgv63cxzXtbF7zRgthAsf0GBMeRZq+mNPrIE2NVkmkktFwBjQ==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-parser-xml": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-parser-xml/-/mjml-parser-xml-4.15.0.tgz",
- "integrity": "sha512-hWuloAR91w4LEntGk3LA1JNxKGAYdAxlgTVg25uEk5OLY5Fko+Uld7QcX3t8DLCMVu9ql83KoLw+J4k4vFNXgA==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "detect-node": "2.1.0",
- "htmlparser2": "^9.1.0",
- "lodash": "^4.17.15"
- }
- },
- "node_modules/mjml-parser-xml/node_modules/htmlparser2": {
- "version": "9.1.0",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
- "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==",
- "funding": [
- "https://github.com/fb55/htmlparser2?sponsor=1",
- {
- "type": "github",
- "url": "https://github.com/sponsors/fb55"
- }
- ],
- "dependencies": {
- "domelementtype": "^2.3.0",
- "domhandler": "^5.0.3",
- "domutils": "^3.1.0",
- "entities": "^4.5.0"
- }
- },
- "node_modules/mjml-preset-core": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-preset-core/-/mjml-preset-core-4.15.0.tgz",
- "integrity": "sha512-x5zHrPj4Emecy3FG+5JJR22qQFA7bg/G8nQotkCtpuxsBghSL+StuDN3Es2t3ARxNwuyew46yOQknbEXMd4tgQ==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "mjml-accordion": "4.15.0",
- "mjml-body": "4.15.0",
- "mjml-button": "4.15.0",
- "mjml-carousel": "4.15.0",
- "mjml-column": "4.15.0",
- "mjml-divider": "4.15.0",
- "mjml-group": "4.15.0",
- "mjml-head": "4.15.0",
- "mjml-head-attributes": "4.15.0",
- "mjml-head-breakpoint": "4.15.0",
- "mjml-head-font": "4.15.0",
- "mjml-head-html-attributes": "4.15.0",
- "mjml-head-preview": "4.15.0",
- "mjml-head-style": "4.15.0",
- "mjml-head-title": "4.15.0",
- "mjml-hero": "4.15.0",
- "mjml-image": "4.15.0",
- "mjml-navbar": "4.15.0",
- "mjml-raw": "4.15.0",
- "mjml-section": "4.15.0",
- "mjml-social": "4.15.0",
- "mjml-spacer": "4.15.0",
- "mjml-table": "4.15.0",
- "mjml-text": "4.15.0",
- "mjml-wrapper": "4.15.0"
- }
- },
- "node_modules/mjml-raw": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-raw/-/mjml-raw-4.15.0.tgz",
- "integrity": "sha512-AFvHfcLgjxjyin03SK/9YJ85IFoGAEKVjdvlycCFiFJgotEaBpl4BVci7qiApGNUo5L3mFXyk4lcF41nIpeEGQ==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-section": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-section/-/mjml-section-4.15.0.tgz",
- "integrity": "sha512-qtOGYW+LpwAvNw/66Hn4XFxmAnOwgb/2DIy8+o7xLCmVDjKdt9CiFFRUFklioA1bxs3tTFxMbI1hNQjWnJdlKg==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-social": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-social/-/mjml-social-4.15.0.tgz",
- "integrity": "sha512-MghH7LIdsl2bVC4uIOdXno3Z5gyod+oYIwHvwrcLe98qQSoybpqBVlzZlvyUOk7slgW8GbaC9wvskvwN9Uw5mg==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-spacer": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-spacer/-/mjml-spacer-4.15.0.tgz",
- "integrity": "sha512-oOmK10ZWJtIx0GDydJz1zT6YIbQPXBIXYVH1dK8Jn6yaZ7fIVPgP9zWDQdSITp2HeP+GR3Ov1LAeUo2bAtV4vQ==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-table": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-table/-/mjml-table-4.15.0.tgz",
- "integrity": "sha512-ZZTkQChK2Jj5cvc0vHXgrDI7m8/PQNPxbeThMduCwPK4JEV4kgkvTI6i9VIaioVNOP/YQ+ACZi1NCbGGQWiUZA==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-text": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-text/-/mjml-text-4.15.0.tgz",
- "integrity": "sha512-gtqSx/Z6D3yYSQhriP2Z0/kAHiYgZE5QXmMBBuvq6owreoiIDRvSgEdldd+S+8halEQIvdRi5gw1RT22SpIpMA==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0"
- }
- },
- "node_modules/mjml-validator": {
- "version": "4.13.0",
- "resolved": "https://registry.npmjs.org/mjml-validator/-/mjml-validator-4.13.0.tgz",
- "integrity": "sha512-uURYfyQYtHJ6Qz/1A7/+E9ezfcoISoLZhYK3olsxKRViwaA2Mm8gy/J3yggZXnsUXWUns7Qymycm5LglLEIiQg==",
- "dependencies": {
- "@babel/runtime": "^7.14.6"
- }
- },
- "node_modules/mjml-wrapper": {
- "version": "4.15.0",
- "resolved": "https://registry.npmjs.org/mjml-wrapper/-/mjml-wrapper-4.15.0.tgz",
- "integrity": "sha512-jPPxnI7ItTBgtAseDTIOGUFitOkisy359MgOITZvGs0cI9vHy9H09q8rAr6D2/XhuFAQ2mLpyy5HqVWOMzACxw==",
- "dependencies": {
- "@babel/runtime": "^7.14.6",
- "lodash": "^4.17.21",
- "mjml-core": "4.15.0",
- "mjml-section": "4.15.0"
- }
- },
- "node_modules/mkdirp": {
- "version": "1.0.4",
- "license": "MIT",
- "bin": {
- "mkdirp": "bin/cmd.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/mlly": {
- "version": "1.6.1",
- "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.6.1.tgz",
- "integrity": "sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA==",
- "dev": true,
- "dependencies": {
- "acorn": "^8.11.3",
- "pathe": "^1.1.2",
- "pkg-types": "^1.0.3",
- "ufo": "^1.3.2"
- }
- },
- "node_modules/moment": {
- "version": "2.29.4",
- "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
- "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/mongodb": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.3.0.tgz",
- "integrity": "sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==",
- "dependencies": {
- "@mongodb-js/saslprep": "^1.1.0",
- "bson": "^6.2.0",
- "mongodb-connection-string-url": "^3.0.0"
- },
- "engines": {
- "node": ">=16.20.1"
- },
- "peerDependencies": {
- "@aws-sdk/credential-providers": "^3.188.0",
- "@mongodb-js/zstd": "^1.1.0",
- "gcp-metadata": "^5.2.0",
- "kerberos": "^2.0.1",
- "mongodb-client-encryption": ">=6.0.0 <7",
- "snappy": "^7.2.2",
- "socks": "^2.7.1"
- },
- "peerDependenciesMeta": {
- "@aws-sdk/credential-providers": {
- "optional": true
- },
- "@mongodb-js/zstd": {
- "optional": true
- },
- "gcp-metadata": {
- "optional": true
- },
- "kerberos": {
- "optional": true
- },
- "mongodb-client-encryption": {
- "optional": true
- },
- "snappy": {
- "optional": true
- },
- "socks": {
- "optional": true
- }
- }
- },
- "node_modules/mongodb-connection-string-url": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz",
- "integrity": "sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==",
- "dependencies": {
- "@types/whatwg-url": "^11.0.2",
- "whatwg-url": "^13.0.0"
- }
- },
- "node_modules/mongodb-connection-string-url/node_modules/tr46": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
- "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
- "dependencies": {
- "punycode": "^2.3.0"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
- "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/mongodb-connection-string-url/node_modules/whatwg-url": {
- "version": "13.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz",
- "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==",
- "dependencies": {
- "tr46": "^4.1.1",
- "webidl-conversions": "^7.0.0"
- },
- "engines": {
- "node": ">=16"
- }
- },
- "node_modules/mongodb-memory-server": {
- "version": "9.2.0",
- "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-9.2.0.tgz",
- "integrity": "sha512-w/usKdYtby5EALERxmA0+et+D0brP0InH3a26shNDgGefXA61hgl6U0P3IfwqZlEGRZdkbZig3n57AHZgDiwvg==",
- "dev": true,
- "hasInstallScript": true,
- "dependencies": {
- "mongodb-memory-server-core": "9.2.0",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=14.20.1"
- }
- },
- "node_modules/mongodb-memory-server-core": {
- "version": "9.2.0",
- "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-9.2.0.tgz",
- "integrity": "sha512-9SWZEy+dGj5Fvm5RY/mtqHZKS64o4heDwReD4SsfR7+uNgtYo+JN41kPCcJeIH3aJf04j25i5Dia2s52KmsMPA==",
- "dev": true,
- "dependencies": {
- "async-mutex": "^0.4.0",
- "camelcase": "^6.3.0",
- "debug": "^4.3.4",
- "find-cache-dir": "^3.3.2",
- "follow-redirects": "^1.15.6",
- "https-proxy-agent": "^7.0.4",
- "mongodb": "^5.9.1",
- "new-find-package-json": "^2.0.0",
- "semver": "^7.6.0",
- "tar-stream": "^3.1.7",
- "tslib": "^2.6.2",
- "yauzl": "^3.1.3"
- },
- "engines": {
- "node": ">=14.20.1"
- }
- },
- "node_modules/mongodb-memory-server-core/node_modules/@types/whatwg-url": {
- "version": "8.2.2",
- "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
- "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
- "dev": true,
- "dependencies": {
- "@types/node": "*",
- "@types/webidl-conversions": "*"
- }
- },
- "node_modules/mongodb-memory-server-core/node_modules/agent-base": {
- "version": "7.1.1",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
- "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
- "dev": true,
- "dependencies": {
- "debug": "^4.3.4"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/mongodb-memory-server-core/node_modules/bson": {
- "version": "5.5.1",
- "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz",
- "integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==",
- "dev": true,
- "engines": {
- "node": ">=14.20.1"
- }
- },
- "node_modules/mongodb-memory-server-core/node_modules/debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "dev": true,
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/mongodb-memory-server-core/node_modules/https-proxy-agent": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz",
- "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==",
- "dev": true,
- "dependencies": {
- "agent-base": "^7.0.2",
- "debug": "4"
- },
- "engines": {
- "node": ">= 14"
- }
- },
- "node_modules/mongodb-memory-server-core/node_modules/mongodb": {
- "version": "5.9.2",
- "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz",
- "integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==",
- "dev": true,
- "dependencies": {
- "bson": "^5.5.0",
- "mongodb-connection-string-url": "^2.6.0",
- "socks": "^2.7.1"
- },
- "engines": {
- "node": ">=14.20.1"
- },
- "optionalDependencies": {
- "@mongodb-js/saslprep": "^1.1.0"
- },
- "peerDependencies": {
- "@aws-sdk/credential-providers": "^3.188.0",
- "@mongodb-js/zstd": "^1.0.0",
- "kerberos": "^1.0.0 || ^2.0.0",
- "mongodb-client-encryption": ">=2.3.0 <3",
- "snappy": "^7.2.2"
- },
- "peerDependenciesMeta": {
- "@aws-sdk/credential-providers": {
- "optional": true
- },
- "@mongodb-js/zstd": {
- "optional": true
- },
- "kerberos": {
- "optional": true
- },
- "mongodb-client-encryption": {
- "optional": true
- },
- "snappy": {
- "optional": true
- }
- }
- },
- "node_modules/mongodb-memory-server-core/node_modules/mongodb-connection-string-url": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz",
- "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==",
- "dev": true,
- "dependencies": {
- "@types/whatwg-url": "^8.2.1",
- "whatwg-url": "^11.0.0"
- }
- },
- "node_modules/mongodb-memory-server-core/node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
- },
- "node_modules/mongodb-memory-server-core/node_modules/tr46": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
- "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
- "dev": true,
- "dependencies": {
- "punycode": "^2.1.1"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/mongodb-memory-server-core/node_modules/webidl-conversions": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
- "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
- "dev": true,
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/mongodb-memory-server-core/node_modules/whatwg-url": {
- "version": "11.0.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
- "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
- "dev": true,
- "dependencies": {
- "tr46": "^3.0.0",
- "webidl-conversions": "^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/ms": {
- "version": "2.0.0",
- "license": "MIT"
- },
- "node_modules/msgpackr": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.8.5.tgz",
- "integrity": "sha512-mpPs3qqTug6ahbblkThoUY2DQdNXcm4IapwOS3Vm/87vmpzLVelvp9h3It1y9l1VPpiFLV11vfOXnmeEwiIXwg==",
- "optionalDependencies": {
- "msgpackr-extract": "^3.0.1"
- }
- },
- "node_modules/msgpackr-extract": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.2.tgz",
- "integrity": "sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==",
- "hasInstallScript": true,
- "optional": true,
- "dependencies": {
- "node-gyp-build-optional-packages": "5.0.7"
- },
- "bin": {
- "download-msgpackr-prebuilds": "bin/download-prebuilds.js"
- },
- "optionalDependencies": {
- "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.2",
- "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.2",
- "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.2",
- "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.2",
- "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.2",
- "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.2"
- }
- },
- "node_modules/mustache": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
- "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
- "bin": {
- "mustache": "bin/mustache"
- }
- },
- "node_modules/nanoid": {
- "version": "3.3.7",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
- "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/negotiator": {
- "version": "0.6.3",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/new-find-package-json": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/new-find-package-json/-/new-find-package-json-2.0.0.tgz",
- "integrity": "sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew==",
- "dev": true,
- "dependencies": {
- "debug": "^4.3.4"
- },
- "engines": {
- "node": ">=12.22.0"
- }
- },
- "node_modules/new-find-package-json/node_modules/debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "dev": true,
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/new-find-package-json/node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
- },
- "node_modules/no-case": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
- "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==",
- "dependencies": {
- "lower-case": "^1.1.1"
- }
- },
- "node_modules/node-addon-api": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
- "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
- },
- "node_modules/node-fetch": {
- "version": "2.6.7",
- "license": "MIT",
- "dependencies": {
- "whatwg-url": "^5.0.0"
- },
- "engines": {
- "node": "4.x || >=6.0.0"
- },
- "peerDependencies": {
- "encoding": "^0.1.0"
- },
- "peerDependenciesMeta": {
- "encoding": {
- "optional": true
- }
- }
- },
- "node_modules/node-forge": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz",
- "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==",
- "engines": {
- "node": ">= 6.13.0"
- }
- },
- "node_modules/node-gyp-build-optional-packages": {
- "version": "5.0.7",
- "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz",
- "integrity": "sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==",
- "optional": true,
- "bin": {
- "node-gyp-build-optional-packages": "bin.js",
- "node-gyp-build-optional-packages-optional": "optional.js",
- "node-gyp-build-optional-packages-test": "build-test.js"
- }
- },
- "node_modules/nodemailer": {
- "version": "6.9.9",
- "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.9.tgz",
- "integrity": "sha512-dexTll8zqQoVJEZPwQAKzxxtFn0qTnjdQTchoU6Re9BUUGBJiOy3YMn/0ShTW6J5M0dfQ1NeDeRTTl4oIWgQMA==",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/nodemon": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz",
- "integrity": "sha512-g9AZ7HmkhQkqXkRc20w+ZfQ73cHLbE8hnPbtaFbFtCumZsjyMhKk9LajQ07U5Ux28lvFjZ5X7HvWR1xzU8jHVw==",
- "dependencies": {
- "chokidar": "^3.5.2",
- "debug": "^3.2.7",
- "ignore-by-default": "^1.0.1",
- "minimatch": "^3.1.2",
- "pstree.remy": "^1.1.8",
- "semver": "^7.5.3",
- "simple-update-notifier": "^2.0.0",
- "supports-color": "^5.5.0",
- "touch": "^3.1.0",
- "undefsafe": "^2.0.5"
- },
- "bin": {
- "nodemon": "bin/nodemon.js"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/nodemon"
- }
- },
- "node_modules/nodemon/node_modules/debug": {
- "version": "3.2.7",
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.1"
- }
- },
- "node_modules/nodemon/node_modules/ms": {
- "version": "2.1.3",
- "license": "MIT"
- },
- "node_modules/nopt": {
- "version": "1.0.10",
- "license": "MIT",
- "dependencies": {
- "abbrev": "1"
- },
- "bin": {
- "nopt": "bin/nopt.js"
- }
- },
- "node_modules/normalize-path": {
- "version": "3.0.0",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/npmlog": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
- "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
- "dependencies": {
- "are-we-there-yet": "^2.0.0",
- "console-control-strings": "^1.1.0",
- "gauge": "^3.0.0",
- "set-blocking": "^2.0.0"
- }
- },
- "node_modules/nth-check": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
- "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
- "dependencies": {
- "boolbase": "^1.0.0"
- },
- "funding": {
- "url": "https://github.com/fb55/nth-check?sponsor=1"
- }
- },
- "node_modules/object-assign": {
- "version": "4.1.1",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/object-hash": {
- "version": "3.0.0",
- "license": "MIT",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/object-inspect": {
- "version": "1.12.0",
- "license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/on-finished": {
- "version": "2.3.0",
- "license": "MIT",
- "dependencies": {
- "ee-first": "1.1.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/once": {
- "version": "1.4.0",
- "license": "ISC",
- "dependencies": {
- "wrappy": "1"
- }
- },
- "node_modules/one-time": {
- "version": "1.0.0",
- "license": "MIT",
- "dependencies": {
- "fn.name": "1.x.x"
- }
- },
- "node_modules/os-tmpdir": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
- "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/p-limit": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
- "optional": true,
- "dependencies": {
- "yocto-queue": "^0.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-locate": {
- "version": "4.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-limit": "^2.2.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/p-locate/node_modules/p-limit": {
- "version": "2.3.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "p-try": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/p-map": {
- "version": "2.1.0",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/p-try": {
- "version": "2.2.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/param-case": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
- "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==",
- "dependencies": {
- "no-case": "^2.2.0"
- }
- },
- "node_modules/parse5": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
- "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
- "dependencies": {
- "entities": "^4.4.0"
- },
- "funding": {
- "url": "https://github.com/inikulin/parse5?sponsor=1"
- }
- },
- "node_modules/parse5-htmlparser2-tree-adapter": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz",
- "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==",
- "dependencies": {
- "domhandler": "^5.0.2",
- "parse5": "^7.0.0"
- },
- "funding": {
- "url": "https://github.com/inikulin/parse5?sponsor=1"
- }
- },
- "node_modules/parseurl": {
- "version": "1.3.3",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/path": {
- "version": "0.12.7",
- "license": "MIT",
- "dependencies": {
- "process": "^0.11.1",
- "util": "^0.10.3"
- }
- },
- "node_modules/path-exists": {
- "version": "4.0.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-is-absolute": {
- "version": "1.0.1",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/path-key": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/path-parse": {
- "version": "1.0.7",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/path-scurry": {
- "version": "1.10.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
- "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
- "dependencies": {
- "lru-cache": "^9.1.1 || ^10.0.0",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/path-scurry/node_modules/lru-cache": {
- "version": "10.2.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
- "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
- "engines": {
- "node": "14 || >=16.14"
- }
- },
- "node_modules/path-scurry/node_modules/minipass": {
- "version": "7.0.4",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
- "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/path-to-regexp": {
- "version": "0.1.7",
- "license": "MIT"
- },
- "node_modules/pathe": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
- "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
- "dev": true
- },
- "node_modules/pathval": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
- "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
- "dev": true,
- "engines": {
- "node": "*"
- }
- },
- "node_modules/pend": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
- "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
- "dev": true
- },
- "node_modules/picocolors": {
- "version": "1.0.0",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/picomatch": {
- "version": "2.3.1",
- "license": "MIT",
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/pino": {
- "version": "6.14.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fast-redact": "^3.0.0",
- "fast-safe-stringify": "^2.0.8",
- "flatstr": "^1.0.12",
- "pino-std-serializers": "^3.1.0",
- "process-warning": "^1.0.0",
- "quick-format-unescaped": "^4.0.3",
- "sonic-boom": "^1.0.2"
- },
- "bin": {
- "pino": "bin.js"
- }
- },
- "node_modules/pino-std-serializers": {
- "version": "3.2.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/pkg-dir": {
- "version": "4.2.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "find-up": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/pkg-types": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz",
- "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==",
- "dev": true,
- "dependencies": {
- "jsonc-parser": "^3.2.0",
- "mlly": "^1.2.0",
- "pathe": "^1.1.0"
- }
- },
- "node_modules/postcss": {
- "version": "8.4.38",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
- "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "dependencies": {
- "nanoid": "^3.3.7",
- "picocolors": "^1.0.0",
- "source-map-js": "^1.2.0"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/pretty-format": {
- "version": "29.7.0",
- "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
- "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
- "dev": true,
- "dependencies": {
- "@jest/schemas": "^29.6.3",
- "ansi-styles": "^5.0.0",
- "react-is": "^18.0.0"
- },
- "engines": {
- "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
- }
- },
- "node_modules/pretty-format/node_modules/ansi-styles": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
- "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
- "dev": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/process": {
- "version": "0.11.10",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6.0"
- }
- },
- "node_modules/process-warning": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/prom-client": {
- "version": "14.0.1",
- "license": "Apache-2.0",
- "dependencies": {
- "tdigest": "^0.1.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/proto-list": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
- "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="
- },
- "node_modules/proto3-json-serializer": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.1.tgz",
- "integrity": "sha512-8awBvjO+FwkMd6gNoGFZyqkHZXCFd54CIYTb6De7dPaufGJ2XNW+QUNqbMr8MaAocMdb+KpsD4rxEOaTBDCffA==",
- "optional": true,
- "dependencies": {
- "protobufjs": "^7.2.5"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/protobufjs": {
- "version": "7.2.6",
- "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.6.tgz",
- "integrity": "sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==",
- "hasInstallScript": true,
- "optional": true,
- "dependencies": {
- "@protobufjs/aspromise": "^1.1.2",
- "@protobufjs/base64": "^1.1.2",
- "@protobufjs/codegen": "^2.0.4",
- "@protobufjs/eventemitter": "^1.1.0",
- "@protobufjs/fetch": "^1.1.0",
- "@protobufjs/float": "^1.0.2",
- "@protobufjs/inquire": "^1.1.0",
- "@protobufjs/path": "^1.1.2",
- "@protobufjs/pool": "^1.1.0",
- "@protobufjs/utf8": "^1.1.0",
- "@types/node": ">=13.7.0",
- "long": "^5.0.0"
- },
- "engines": {
- "node": ">=12.0.0"
- }
- },
- "node_modules/proxy-addr": {
- "version": "2.0.7",
- "license": "MIT",
- "dependencies": {
- "forwarded": "0.2.0",
- "ipaddr.js": "1.9.1"
- },
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/proxy-from-env": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
- },
- "node_modules/pseudomap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
- "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ=="
- },
- "node_modules/pstree.remy": {
- "version": "1.1.8",
- "license": "MIT"
- },
- "node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/qs": {
- "version": "6.9.7",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz",
- "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==",
- "engines": {
- "node": ">=0.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/queue-microtask": {
- "version": "1.2.3",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/queue-tick": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
- "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==",
- "dev": true
- },
- "node_modules/quick-format-unescaped": {
- "version": "4.0.4",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/range-parser": {
- "version": "1.2.1",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/rate-limiter-flexible": {
- "version": "2.3.7",
- "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-2.3.7.tgz",
- "integrity": "sha512-dmc+J/IffVBvHlqq5/XClsdLdkOdQV/tjrz00cwneHUbEDYVrf4aUDAyR4Jybcf2+Vpn4NwoVrnnAyt/D0ciWw=="
- },
- "node_modules/raw-body": {
- "version": "2.4.3",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz",
- "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==",
- "dependencies": {
- "bytes": "3.1.2",
- "http-errors": "1.8.1",
- "iconv-lite": "0.4.24",
- "unpipe": "1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/react-is": {
- "version": "18.3.1",
- "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
- "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
- "dev": true
- },
- "node_modules/readable-stream": {
- "version": "3.6.0",
- "license": "MIT",
- "dependencies": {
- "inherits": "^2.0.3",
- "string_decoder": "^1.1.1",
- "util-deprecate": "^1.0.1"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/readdirp": {
- "version": "3.6.0",
- "license": "MIT",
- "dependencies": {
- "picomatch": "^2.2.1"
- },
- "engines": {
- "node": ">=8.10.0"
- }
- },
- "node_modules/readline-sync": {
- "version": "1.4.10",
- "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz",
- "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==",
- "dev": true,
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/redis-commands": {
- "version": "1.7.0",
- "license": "MIT"
- },
- "node_modules/redis-errors": {
- "version": "1.2.0",
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/redis-parser": {
- "version": "3.0.0",
- "license": "MIT",
- "dependencies": {
- "redis-errors": "^1.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/regenerator-runtime": {
- "version": "0.14.1",
- "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
- "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
- },
- "node_modules/relateurl": {
- "version": "0.2.7",
- "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
- "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==",
- "engines": {
- "node": ">= 0.10"
- }
- },
- "node_modules/require-directory": {
- "version": "2.1.1",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/require-from-string": {
- "version": "2.0.2",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/resolve": {
- "version": "1.22.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-core-module": "^2.8.1",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- },
- "bin": {
- "resolve": "bin/resolve"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/ret": {
- "version": "0.2.2",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/retry": {
- "version": "0.13.1",
- "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz",
- "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==",
- "optional": true,
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/retry-request": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz",
- "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==",
- "optional": true,
- "dependencies": {
- "@types/request": "^2.48.8",
- "extend": "^3.0.2",
- "teeny-request": "^9.0.0"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/reusify": {
- "version": "1.0.4",
- "dev": true,
- "license": "MIT",
- "engines": {
- "iojs": ">=1.0.0",
- "node": ">=0.10.0"
- }
- },
- "node_modules/rfdc": {
- "version": "1.3.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "dependencies": {
- "glob": "^7.1.3"
- },
- "bin": {
- "rimraf": "bin.js"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/rollup": {
- "version": "4.17.2",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz",
- "integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==",
- "dev": true,
- "dependencies": {
- "@types/estree": "1.0.5"
- },
- "bin": {
- "rollup": "dist/bin/rollup"
- },
- "engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
- },
- "optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.17.2",
- "@rollup/rollup-android-arm64": "4.17.2",
- "@rollup/rollup-darwin-arm64": "4.17.2",
- "@rollup/rollup-darwin-x64": "4.17.2",
- "@rollup/rollup-linux-arm-gnueabihf": "4.17.2",
- "@rollup/rollup-linux-arm-musleabihf": "4.17.2",
- "@rollup/rollup-linux-arm64-gnu": "4.17.2",
- "@rollup/rollup-linux-arm64-musl": "4.17.2",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.17.2",
- "@rollup/rollup-linux-riscv64-gnu": "4.17.2",
- "@rollup/rollup-linux-s390x-gnu": "4.17.2",
- "@rollup/rollup-linux-x64-gnu": "4.17.2",
- "@rollup/rollup-linux-x64-musl": "4.17.2",
- "@rollup/rollup-win32-arm64-msvc": "4.17.2",
- "@rollup/rollup-win32-ia32-msvc": "4.17.2",
- "@rollup/rollup-win32-x64-msvc": "4.17.2",
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/safe-buffer": {
- "version": "5.1.2",
- "license": "MIT"
- },
- "node_modules/safe-regex2": {
- "version": "2.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ret": "~0.2.0"
- }
- },
- "node_modules/safe-stable-stringify": {
- "version": "2.3.1",
- "license": "MIT",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/safer-buffer": {
- "version": "2.1.2",
- "license": "MIT"
- },
- "node_modules/secure-json-parse": {
- "version": "2.4.0",
- "dev": true,
- "license": "BSD-3-Clause"
- },
- "node_modules/semver": {
- "version": "7.6.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
- "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
- "dependencies": {
- "lru-cache": "^6.0.0"
- },
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/semver-store": {
- "version": "0.3.0",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/semver/node_modules/lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dependencies": {
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/send": {
- "version": "0.17.2",
- "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz",
- "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==",
- "dependencies": {
- "debug": "2.6.9",
- "depd": "~1.1.2",
- "destroy": "~1.0.4",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "fresh": "0.5.2",
- "http-errors": "1.8.1",
- "mime": "1.6.0",
- "ms": "2.1.3",
- "on-finished": "~2.3.0",
- "range-parser": "~1.2.1",
- "statuses": "~1.5.0"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/send/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
- },
- "node_modules/serve-static": {
- "version": "1.14.2",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz",
- "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==",
- "dependencies": {
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "parseurl": "~1.3.3",
- "send": "0.17.2"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/set-blocking": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
- "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
- },
- "node_modules/set-cookie-parser": {
- "version": "2.4.8",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/setprototypeof": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
- "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
- },
- "node_modules/shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "dependencies": {
- "shebang-regex": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/side-channel": {
- "version": "1.0.4",
- "license": "MIT",
- "dependencies": {
- "call-bind": "^1.0.0",
- "get-intrinsic": "^1.0.2",
- "object-inspect": "^1.9.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/siginfo": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
- "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
- "dev": true
- },
- "node_modules/signal-exit": {
- "version": "3.0.7",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
- "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
- },
- "node_modules/simple-git": {
- "version": "3.16.0",
- "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.16.0.tgz",
- "integrity": "sha512-zuWYsOLEhbJRWVxpjdiXl6eyAyGo/KzVW+KFhhw9MqEEJttcq+32jTWSGyxTdf9e/YCohxRE+9xpWFj9FdiJNw==",
- "dependencies": {
- "@kwsites/file-exists": "^1.1.1",
- "@kwsites/promise-deferred": "^1.1.1",
- "debug": "^4.3.4"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/steveukx/git-js?sponsor=1"
- }
- },
- "node_modules/simple-git/node_modules/debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/simple-git/node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- },
- "node_modules/simple-swizzle": {
- "version": "0.2.2",
- "license": "MIT",
- "dependencies": {
- "is-arrayish": "^0.3.1"
- }
- },
- "node_modules/simple-update-notifier": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
- "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
- "dependencies": {
- "semver": "^7.5.3"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/slick": {
- "version": "1.12.2",
- "resolved": "https://registry.npmjs.org/slick/-/slick-1.12.2.tgz",
- "integrity": "sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A==",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/smart-buffer": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
- "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
- "devOptional": true,
- "engines": {
- "node": ">= 6.0.0",
- "npm": ">= 3.0.0"
- }
- },
- "node_modules/socks": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz",
- "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==",
- "devOptional": true,
- "dependencies": {
- "ip": "^2.0.0",
- "smart-buffer": "^4.2.0"
- },
- "engines": {
- "node": ">= 10.13.0",
- "npm": ">= 3.0.0"
- }
- },
- "node_modules/sonic-boom": {
- "version": "1.4.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "atomic-sleep": "^1.0.0",
- "flatstr": "^1.0.12"
- }
- },
- "node_modules/source-map": {
- "version": "0.6.1",
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/source-map-js": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
- "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/source-map-support": {
- "version": "0.5.21",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "buffer-from": "^1.0.0",
- "source-map": "^0.6.0"
- }
- },
- "node_modules/sparse-bitfield": {
- "version": "3.0.3",
- "license": "MIT",
- "dependencies": {
- "memory-pager": "^1.0.2"
- }
- },
- "node_modules/stack-trace": {
- "version": "0.0.10",
- "license": "MIT",
- "engines": {
- "node": "*"
- }
- },
- "node_modules/stackback": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
- "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
- "dev": true
- },
- "node_modules/standard-as-callback": {
- "version": "2.1.0",
- "license": "MIT"
- },
- "node_modules/statuses": {
- "version": "1.5.0",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/std-env": {
- "version": "3.7.0",
- "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz",
- "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==",
- "dev": true
- },
- "node_modules/stream-events": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz",
- "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==",
- "optional": true,
- "dependencies": {
- "stubs": "^3.0.0"
- }
- },
- "node_modules/stream-shift": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz",
- "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==",
- "optional": true
- },
- "node_modules/streamx": {
- "version": "2.16.1",
- "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz",
- "integrity": "sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==",
- "dev": true,
- "dependencies": {
- "fast-fifo": "^1.1.0",
- "queue-tick": "^1.0.1"
- },
- "optionalDependencies": {
- "bare-events": "^2.2.0"
- }
- },
- "node_modules/string_decoder": {
- "version": "1.3.0",
- "license": "MIT",
- "dependencies": {
- "safe-buffer": "~5.2.0"
- }
- },
- "node_modules/string_decoder/node_modules/safe-buffer": {
- "version": "5.2.1",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/string-similarity": {
- "version": "4.0.4",
- "license": "ISC"
- },
- "node_modules/string-width": {
- "version": "4.2.3",
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/string-width-cjs": {
- "name": "string-width",
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi": {
- "version": "6.0.1",
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi-cjs": {
- "name": "strip-ansi",
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-json-comments": {
- "version": "2.0.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/strip-literal": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.0.tgz",
- "integrity": "sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==",
- "dev": true,
- "dependencies": {
- "js-tokens": "^9.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/antfu"
- }
- },
- "node_modules/strip-literal/node_modules/js-tokens": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.0.tgz",
- "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==",
- "dev": true
- },
- "node_modules/strnum": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz",
- "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==",
- "optional": true
- },
- "node_modules/stubs": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz",
- "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==",
- "optional": true
- },
- "node_modules/superagent": {
- "version": "7.1.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "component-emitter": "^1.3.0",
- "cookiejar": "^2.1.3",
- "debug": "^4.3.4",
- "fast-safe-stringify": "^2.1.1",
- "form-data": "^4.0.0",
- "formidable": "^2.0.1",
- "methods": "^1.1.2",
- "mime": "^2.5.0",
- "qs": "^6.10.3",
- "readable-stream": "^3.6.0",
- "semver": "^7.3.7"
- },
- "engines": {
- "node": ">=6.4.0 <13 || >=14"
- }
- },
- "node_modules/superagent/node_modules/debug": {
- "version": "4.3.4",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/superagent/node_modules/form-data": {
- "version": "4.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/superagent/node_modules/mime": {
- "version": "2.6.0",
- "dev": true,
- "license": "MIT",
- "bin": {
- "mime": "cli.js"
- },
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/superagent/node_modules/ms": {
- "version": "2.1.2",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/superagent/node_modules/qs": {
- "version": "6.10.3",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "side-channel": "^1.0.4"
- },
- "engines": {
- "node": ">=0.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/supertest": {
- "version": "6.2.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "methods": "^1.1.2",
- "superagent": "^7.1.3"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/supports-color": {
- "version": "5.5.0",
- "license": "MIT",
- "dependencies": {
- "has-flag": "^3.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/supports-preserve-symlinks-flag": {
- "version": "1.0.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/swagger-stats": {
- "version": "0.99.5",
- "resolved": "https://registry.npmjs.org/swagger-stats/-/swagger-stats-0.99.5.tgz",
- "integrity": "sha512-OdDn9AUYyiTiMR4peSJxgC1fXmx9AM55NdkQAcQ1DFAXHktrjK2Z3cpLrSZ3e+lW1VZQ6mBGf/L2oNgSGmK0zw==",
- "dependencies": {
- "axios": "^1.2.2",
- "basic-auth": "^2.0.1",
- "cookies": "^0.8.0",
- "debug": "^4.3.4",
- "moment": "^2.29.4",
- "path-to-regexp": "^6.2.1",
- "qs": "^6.11.0",
- "send": "^0.18.0",
- "uuid": "^9.0.0"
- },
- "peerDependencies": {
- "prom-client": ">= 10 <= 14"
- }
- },
- "node_modules/swagger-stats/node_modules/debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/swagger-stats/node_modules/depd": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
- "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/swagger-stats/node_modules/destroy": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
- "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
- "engines": {
- "node": ">= 0.8",
- "npm": "1.2.8000 || >= 1.4.16"
- }
- },
- "node_modules/swagger-stats/node_modules/http-errors": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
- "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
- "dependencies": {
- "depd": "2.0.0",
- "inherits": "2.0.4",
- "setprototypeof": "1.2.0",
- "statuses": "2.0.1",
- "toidentifier": "1.0.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/swagger-stats/node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
- },
- "node_modules/swagger-stats/node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
- },
- "node_modules/swagger-stats/node_modules/on-finished": {
- "version": "2.4.1",
- "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
- "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
- "dependencies": {
- "ee-first": "1.1.1"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/swagger-stats/node_modules/path-to-regexp": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz",
- "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw=="
- },
- "node_modules/swagger-stats/node_modules/qs": {
- "version": "6.11.0",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
- "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
- "dependencies": {
- "side-channel": "^1.0.4"
- },
- "engines": {
- "node": ">=0.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/swagger-stats/node_modules/send": {
- "version": "0.18.0",
- "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
- "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
- "dependencies": {
- "debug": "2.6.9",
- "depd": "2.0.0",
- "destroy": "1.2.0",
- "encodeurl": "~1.0.2",
- "escape-html": "~1.0.3",
- "etag": "~1.8.1",
- "fresh": "0.5.2",
- "http-errors": "2.0.0",
- "mime": "1.6.0",
- "ms": "2.1.3",
- "on-finished": "2.4.1",
- "range-parser": "~1.2.1",
- "statuses": "2.0.1"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/swagger-stats/node_modules/send/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/swagger-stats/node_modules/send/node_modules/debug/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
- },
- "node_modules/swagger-stats/node_modules/send/node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
- },
- "node_modules/swagger-stats/node_modules/statuses": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
- "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/swagger-ui-dist": {
- "version": "4.6.1",
- "license": "Apache-2.0"
- },
- "node_modules/swagger-ui-express": {
- "version": "4.3.0",
- "license": "MIT",
- "dependencies": {
- "swagger-ui-dist": ">=4.1.3"
- },
- "engines": {
- "node": ">= v0.10.32"
- },
- "peerDependencies": {
- "express": ">=4.0.0"
- }
- },
- "node_modules/tar": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
- "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
- "dependencies": {
- "chownr": "^2.0.0",
- "fs-minipass": "^2.0.0",
- "minipass": "^5.0.0",
- "minizlib": "^2.1.1",
- "mkdirp": "^1.0.3",
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/tar-stream": {
- "version": "3.1.7",
- "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
- "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==",
- "dev": true,
- "dependencies": {
- "b4a": "^1.6.4",
- "fast-fifo": "^1.2.0",
- "streamx": "^2.15.0"
- }
- },
- "node_modules/tdigest": {
- "version": "0.1.1",
- "license": "MIT",
- "dependencies": {
- "bintrees": "1.0.1"
- }
- },
- "node_modules/teeny-request": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz",
- "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==",
- "optional": true,
- "dependencies": {
- "http-proxy-agent": "^5.0.0",
- "https-proxy-agent": "^5.0.0",
- "node-fetch": "^2.6.9",
- "stream-events": "^1.0.5",
- "uuid": "^9.0.0"
- },
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/teeny-request/node_modules/node-fetch": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
- "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
- "optional": true,
- "dependencies": {
- "whatwg-url": "^5.0.0"
- },
- "engines": {
- "node": "4.x || >=6.0.0"
- },
- "peerDependencies": {
- "encoding": "^0.1.0"
- },
- "peerDependenciesMeta": {
- "encoding": {
- "optional": true
- }
- }
- },
- "node_modules/test-exclude": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
- "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
- "dev": true,
- "dependencies": {
- "@istanbuljs/schema": "^0.1.2",
- "glob": "^7.1.4",
- "minimatch": "^3.0.4"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/text-decoding": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz",
- "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA=="
- },
- "node_modules/text-hex": {
- "version": "1.0.0",
- "license": "MIT"
- },
- "node_modules/tiny-lru": {
- "version": "8.0.1",
- "dev": true,
- "license": "BSD-3-Clause",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/tinybench": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.7.0.tgz",
- "integrity": "sha512-Qgayeb106x2o4hNzNjsZEfFziw8IbKqtbXBjVh7VIZfBxfD5M4gWtpyx5+YTae2gJ6Y6Dz/KLepiv16RFeQWNA==",
- "dev": true
- },
- "node_modules/tinypool": {
- "version": "0.8.4",
- "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz",
- "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==",
- "dev": true,
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/tinyspy": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz",
- "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==",
- "dev": true,
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/to-fast-properties": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
- "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/to-regex-range": {
- "version": "5.0.1",
- "license": "MIT",
- "dependencies": {
- "is-number": "^7.0.0"
- },
- "engines": {
- "node": ">=8.0"
- }
- },
- "node_modules/toidentifier": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
- "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
- "engines": {
- "node": ">=0.6"
- }
- },
- "node_modules/touch": {
- "version": "3.1.0",
- "license": "ISC",
- "dependencies": {
- "nopt": "~1.0.10"
- },
- "bin": {
- "nodetouch": "bin/nodetouch.js"
- }
- },
- "node_modules/tr46": {
- "version": "0.0.3",
- "license": "MIT"
- },
- "node_modules/tree-kill": {
- "version": "1.2.2",
- "dev": true,
- "license": "MIT",
- "bin": {
- "tree-kill": "cli.js"
- }
- },
- "node_modules/triple-beam": {
- "version": "1.3.0",
- "license": "MIT"
- },
- "node_modules/ts-node": {
- "version": "10.9.1",
- "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
- "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
- "dev": true,
- "dependencies": {
- "@cspotcode/source-map-support": "^0.8.0",
- "@tsconfig/node10": "^1.0.7",
- "@tsconfig/node12": "^1.0.7",
- "@tsconfig/node14": "^1.0.0",
- "@tsconfig/node16": "^1.0.2",
- "acorn": "^8.4.1",
- "acorn-walk": "^8.1.1",
- "arg": "^4.1.0",
- "create-require": "^1.1.0",
- "diff": "^4.0.1",
- "make-error": "^1.1.1",
- "v8-compile-cache-lib": "^3.0.1",
- "yn": "3.1.1"
- },
- "bin": {
- "ts-node": "dist/bin.js",
- "ts-node-cwd": "dist/bin-cwd.js",
- "ts-node-esm": "dist/bin-esm.js",
- "ts-node-script": "dist/bin-script.js",
- "ts-node-transpile-only": "dist/bin-transpile.js",
- "ts-script": "dist/bin-script-deprecated.js"
- },
- "peerDependencies": {
- "@swc/core": ">=1.2.50",
- "@swc/wasm": ">=1.2.50",
- "@types/node": "*",
- "typescript": ">=2.7"
- },
- "peerDependenciesMeta": {
- "@swc/core": {
- "optional": true
- },
- "@swc/wasm": {
- "optional": true
- }
- }
- },
- "node_modules/ts-node-dev": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz",
- "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==",
- "dev": true,
- "dependencies": {
- "chokidar": "^3.5.1",
- "dynamic-dedupe": "^0.3.0",
- "minimist": "^1.2.6",
- "mkdirp": "^1.0.4",
- "resolve": "^1.0.0",
- "rimraf": "^2.6.1",
- "source-map-support": "^0.5.12",
- "tree-kill": "^1.2.2",
- "ts-node": "^10.4.0",
- "tsconfig": "^7.0.0"
- },
- "bin": {
- "ts-node-dev": "lib/bin.js",
- "tsnd": "lib/bin.js"
- },
- "engines": {
- "node": ">=0.8.0"
- },
- "peerDependencies": {
- "node-notifier": "*",
- "typescript": "*"
- },
- "peerDependenciesMeta": {
- "node-notifier": {
- "optional": true
- }
- }
- },
- "node_modules/ts-node-dev/node_modules/rimraf": {
- "version": "2.7.1",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "glob": "^7.1.3"
- },
- "bin": {
- "rimraf": "bin.js"
- }
- },
- "node_modules/tsconfig": {
- "version": "7.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/strip-bom": "^3.0.0",
- "@types/strip-json-comments": "0.0.30",
- "strip-bom": "^3.0.0",
- "strip-json-comments": "^2.0.0"
- }
- },
- "node_modules/tsconfig/node_modules/strip-bom": {
- "version": "3.0.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/tslib": {
- "version": "2.6.2",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
- "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
- },
- "node_modules/tsscmp": {
- "version": "1.0.6",
- "license": "MIT",
- "engines": {
- "node": ">=0.6.x"
- }
- },
- "node_modules/type-detect": {
- "version": "4.0.8",
- "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
- "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/type-is": {
- "version": "1.6.18",
- "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
- "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
- "dependencies": {
- "media-typer": "0.3.0",
- "mime-types": "~2.1.24"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/typescript": {
- "version": "5.3.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
- "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
- "dev": true,
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
- "node_modules/ua-parser-js": {
- "version": "0.7.28",
- "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz",
- "integrity": "sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/ua-parser-js"
- },
- {
- "type": "paypal",
- "url": "https://paypal.me/faisalman"
- }
- ],
- "engines": {
- "node": "*"
- }
- },
- "node_modules/ufo": {
- "version": "1.5.3",
- "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz",
- "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==",
- "dev": true
- },
- "node_modules/uglify-js": {
- "version": "3.17.4",
- "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz",
- "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==",
- "bin": {
- "uglifyjs": "bin/uglifyjs"
- },
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/undefsafe": {
- "version": "2.0.5",
- "license": "MIT"
- },
- "node_modules/undici-types": {
- "version": "5.26.5",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
- "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
- },
- "node_modules/unpipe": {
- "version": "1.0.0",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/upper-case": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
- "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA=="
- },
- "node_modules/uri-js": {
- "version": "4.4.1",
- "dev": true,
- "license": "BSD-2-Clause",
- "dependencies": {
- "punycode": "^2.1.0"
- }
- },
- "node_modules/util": {
- "version": "0.10.4",
- "license": "MIT",
- "dependencies": {
- "inherits": "2.0.3"
- }
- },
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "license": "MIT"
- },
- "node_modules/utils-merge": {
- "version": "1.0.1",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4.0"
- }
- },
- "node_modules/uuid": {
- "version": "9.0.1",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
- "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
- "funding": [
- "https://github.com/sponsors/broofa",
- "https://github.com/sponsors/ctavan"
- ],
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
- "node_modules/v8-compile-cache-lib": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
- "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
- "dev": true
- },
- "node_modules/valid-data-url": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-3.0.1.tgz",
- "integrity": "sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA==",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/vary": {
- "version": "1.1.2",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/vite": {
- "version": "5.2.11",
- "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz",
- "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==",
- "dev": true,
- "dependencies": {
- "esbuild": "^0.20.1",
- "postcss": "^8.4.38",
- "rollup": "^4.13.0"
- },
- "bin": {
- "vite": "bin/vite.js"
- },
- "engines": {
- "node": "^18.0.0 || >=20.0.0"
- },
- "funding": {
- "url": "https://github.com/vitejs/vite?sponsor=1"
- },
- "optionalDependencies": {
- "fsevents": "~2.3.3"
- },
- "peerDependencies": {
- "@types/node": "^18.0.0 || >=20.0.0",
- "less": "*",
- "lightningcss": "^1.21.0",
- "sass": "*",
- "stylus": "*",
- "sugarss": "*",
- "terser": "^5.4.0"
- },
- "peerDependenciesMeta": {
- "@types/node": {
- "optional": true
- },
- "less": {
- "optional": true
- },
- "lightningcss": {
- "optional": true
- },
- "sass": {
- "optional": true
- },
- "stylus": {
- "optional": true
- },
- "sugarss": {
- "optional": true
- },
- "terser": {
- "optional": true
- }
- }
- },
- "node_modules/vite-node": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.0.tgz",
- "integrity": "sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==",
- "dev": true,
- "dependencies": {
- "cac": "^6.7.14",
- "debug": "^4.3.4",
- "pathe": "^1.1.1",
- "picocolors": "^1.0.0",
- "vite": "^5.0.0"
- },
- "bin": {
- "vite-node": "vite-node.mjs"
- },
- "engines": {
- "node": "^18.0.0 || >=20.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- }
- },
- "node_modules/vite-node/node_modules/debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "dev": true,
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/vite-node/node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
- },
- "node_modules/vitest": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz",
- "integrity": "sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==",
- "dev": true,
- "dependencies": {
- "@vitest/expect": "1.6.0",
- "@vitest/runner": "1.6.0",
- "@vitest/snapshot": "1.6.0",
- "@vitest/spy": "1.6.0",
- "@vitest/utils": "1.6.0",
- "acorn-walk": "^8.3.2",
- "chai": "^4.3.10",
- "debug": "^4.3.4",
- "execa": "^8.0.1",
- "local-pkg": "^0.5.0",
- "magic-string": "^0.30.5",
- "pathe": "^1.1.1",
- "picocolors": "^1.0.0",
- "std-env": "^3.5.0",
- "strip-literal": "^2.0.0",
- "tinybench": "^2.5.1",
- "tinypool": "^0.8.3",
- "vite": "^5.0.0",
- "vite-node": "1.6.0",
- "why-is-node-running": "^2.2.2"
- },
- "bin": {
- "vitest": "vitest.mjs"
- },
- "engines": {
- "node": "^18.0.0 || >=20.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/vitest"
- },
- "peerDependencies": {
- "@edge-runtime/vm": "*",
- "@types/node": "^18.0.0 || >=20.0.0",
- "@vitest/browser": "1.6.0",
- "@vitest/ui": "1.6.0",
- "happy-dom": "*",
- "jsdom": "*"
- },
- "peerDependenciesMeta": {
- "@edge-runtime/vm": {
- "optional": true
- },
- "@types/node": {
- "optional": true
- },
- "@vitest/browser": {
- "optional": true
- },
- "@vitest/ui": {
- "optional": true
- },
- "happy-dom": {
- "optional": true
- },
- "jsdom": {
- "optional": true
- }
- }
- },
- "node_modules/vitest-mongodb": {
- "version": "0.0.5",
- "resolved": "https://registry.npmjs.org/vitest-mongodb/-/vitest-mongodb-0.0.5.tgz",
- "integrity": "sha512-47xRQcarT9dmInL8QPctJdGdRP9jYveJ6T0zCnbbvLd3z8kH0ZcWpo4oAwyK4++3EltfEcQnUEwUUiDUQMsm+g==",
- "dev": true,
- "dependencies": {
- "debug": "^4.3.4",
- "mongodb-memory-server": "^8.12.0"
- }
- },
- "node_modules/vitest-mongodb/node_modules/debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "dev": true,
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/vitest-mongodb/node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
- },
- "node_modules/vitest/node_modules/debug": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
- "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
- "dev": true,
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/vitest/node_modules/execa": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
- "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
- "dev": true,
- "dependencies": {
- "cross-spawn": "^7.0.3",
- "get-stream": "^8.0.1",
- "human-signals": "^5.0.0",
- "is-stream": "^3.0.0",
- "merge-stream": "^2.0.0",
- "npm-run-path": "^5.1.0",
- "onetime": "^6.0.0",
- "signal-exit": "^4.1.0",
- "strip-final-newline": "^3.0.0"
- },
- "engines": {
- "node": ">=16.17"
- },
- "funding": {
- "url": "https://github.com/sindresorhus/execa?sponsor=1"
- }
- },
- "node_modules/vitest/node_modules/get-stream": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
- "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
- "dev": true,
- "engines": {
- "node": ">=16"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/vitest/node_modules/human-signals": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
- "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
- "dev": true,
- "engines": {
- "node": ">=16.17.0"
- }
- },
- "node_modules/vitest/node_modules/is-stream": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
- "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
- "dev": true,
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/vitest/node_modules/mimic-fn": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
- "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
- "dev": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/vitest/node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
- },
- "node_modules/vitest/node_modules/npm-run-path": {
- "version": "5.3.0",
- "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
- "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
- "dev": true,
- "dependencies": {
- "path-key": "^4.0.0"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/vitest/node_modules/onetime": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
- "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
- "dev": true,
- "dependencies": {
- "mimic-fn": "^4.0.0"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/vitest/node_modules/path-key": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
- "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
- "dev": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/vitest/node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "dev": true,
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/vitest/node_modules/strip-final-newline": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
- "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
- "dev": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/web-resource-inliner": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/web-resource-inliner/-/web-resource-inliner-6.0.1.tgz",
- "integrity": "sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==",
- "dependencies": {
- "ansi-colors": "^4.1.1",
- "escape-goat": "^3.0.0",
- "htmlparser2": "^5.0.0",
- "mime": "^2.4.6",
- "node-fetch": "^2.6.0",
- "valid-data-url": "^3.0.0"
- },
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/web-resource-inliner/node_modules/dom-serializer": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz",
- "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==",
- "dependencies": {
- "domelementtype": "^2.0.1",
- "domhandler": "^4.2.0",
- "entities": "^2.0.0"
- },
- "funding": {
- "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
- }
- },
- "node_modules/web-resource-inliner/node_modules/dom-serializer/node_modules/domhandler": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
- "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
- "dependencies": {
- "domelementtype": "^2.2.0"
- },
- "engines": {
- "node": ">= 4"
- },
- "funding": {
- "url": "https://github.com/fb55/domhandler?sponsor=1"
- }
- },
- "node_modules/web-resource-inliner/node_modules/domhandler": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.3.0.tgz",
- "integrity": "sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==",
- "dependencies": {
- "domelementtype": "^2.0.1"
- },
- "engines": {
- "node": ">= 4"
- },
- "funding": {
- "url": "https://github.com/fb55/domhandler?sponsor=1"
- }
- },
- "node_modules/web-resource-inliner/node_modules/domutils": {
- "version": "2.8.0",
- "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz",
- "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==",
- "dependencies": {
- "dom-serializer": "^1.0.1",
- "domelementtype": "^2.2.0",
- "domhandler": "^4.2.0"
- },
- "funding": {
- "url": "https://github.com/fb55/domutils?sponsor=1"
- }
- },
- "node_modules/web-resource-inliner/node_modules/domutils/node_modules/domhandler": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz",
- "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==",
- "dependencies": {
- "domelementtype": "^2.2.0"
- },
- "engines": {
- "node": ">= 4"
- },
- "funding": {
- "url": "https://github.com/fb55/domhandler?sponsor=1"
- }
- },
- "node_modules/web-resource-inliner/node_modules/entities": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
- "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
- "funding": {
- "url": "https://github.com/fb55/entities?sponsor=1"
- }
- },
- "node_modules/web-resource-inliner/node_modules/htmlparser2": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-5.0.1.tgz",
- "integrity": "sha512-vKZZra6CSe9qsJzh0BjBGXo8dvzNsq/oGvsjfRdOrrryfeD9UOBEEQdeoqCRmKZchF5h2zOBMQ6YuQ0uRUmdbQ==",
- "dependencies": {
- "domelementtype": "^2.0.1",
- "domhandler": "^3.3.0",
- "domutils": "^2.4.2",
- "entities": "^2.0.0"
- },
- "funding": {
- "url": "https://github.com/fb55/htmlparser2?sponsor=1"
- }
- },
- "node_modules/web-resource-inliner/node_modules/mime": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
- "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
- "bin": {
- "mime": "cli.js"
- },
- "engines": {
- "node": ">=4.0.0"
- }
- },
- "node_modules/webidl-conversions": {
- "version": "3.0.1",
- "license": "BSD-2-Clause"
- },
- "node_modules/websocket-driver": {
- "version": "0.7.4",
- "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
- "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==",
- "dependencies": {
- "http-parser-js": ">=0.5.1",
- "safe-buffer": ">=5.1.0",
- "websocket-extensions": ">=0.1.1"
- },
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/websocket-extensions": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
- "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
- "engines": {
- "node": ">=0.8.0"
- }
- },
- "node_modules/whatwg-url": {
- "version": "5.0.0",
- "license": "MIT",
- "dependencies": {
- "tr46": "~0.0.3",
- "webidl-conversions": "^3.0.0"
- }
- },
- "node_modules/which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/why-is-node-running": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz",
- "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==",
- "dev": true,
- "dependencies": {
- "siginfo": "^2.0.0",
- "stackback": "0.0.2"
- },
- "bin": {
- "why-is-node-running": "cli.js"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/wide-align": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
- "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
- "dependencies": {
- "string-width": "^1.0.2 || 2 || 3 || 4"
- }
- },
- "node_modules/winston": {
- "version": "3.6.0",
- "license": "MIT",
- "dependencies": {
- "@dabh/diagnostics": "^2.0.2",
- "async": "^3.2.3",
- "is-stream": "^2.0.0",
- "logform": "^2.4.0",
- "one-time": "^1.0.0",
- "readable-stream": "^3.4.0",
- "safe-stable-stringify": "^2.3.1",
- "stack-trace": "0.0.x",
- "triple-beam": "^1.3.0",
- "winston-transport": "^4.5.0"
- },
- "engines": {
- "node": ">= 12.0.0"
- }
- },
- "node_modules/winston-transport": {
- "version": "4.5.0",
- "license": "MIT",
- "dependencies": {
- "logform": "^2.3.2",
- "readable-stream": "^3.6.0",
- "triple-beam": "^1.3.0"
- },
- "engines": {
- "node": ">= 6.4.0"
- }
- },
- "node_modules/wrap-ansi": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrap-ansi-cjs": {
- "name": "wrap-ansi",
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrappy": {
- "version": "1.0.2",
- "license": "ISC"
- },
- "node_modules/xtend": {
- "version": "4.0.2",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.4"
- }
- },
- "node_modules/y18n": {
- "version": "5.0.8",
- "license": "ISC",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/yallist": {
- "version": "4.0.0",
- "license": "ISC"
- },
- "node_modules/yargs": {
- "version": "17.7.2",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
- "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
- "dependencies": {
- "cliui": "^8.0.1",
- "escalade": "^3.1.1",
- "get-caller-file": "^2.0.5",
- "require-directory": "^2.1.1",
- "string-width": "^4.2.3",
- "y18n": "^5.0.5",
- "yargs-parser": "^21.1.1"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/yargs-parser": {
- "version": "21.1.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
- "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/yauzl": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.1.3.tgz",
- "integrity": "sha512-JCCdmlJJWv7L0q/KylOekyRaUrdEoUxWkWVcgorosTROCFWiS9p2NNPE9Yb91ak7b1N5SxAZEliWpspbZccivw==",
- "dev": true,
- "dependencies": {
- "buffer-crc32": "~0.2.3",
- "pend": "~1.2.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/yn": {
- "version": "3.1.1",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/yocto-queue": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
- "optional": true,
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- }
- }
-}
diff --git a/backend/package.json b/backend/package.json
index 4dba879b0363..7704dfe4fa73 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -1,34 +1,40 @@
{
- "name": "monkeytype-backend",
+ "name": "@monkeytype/backend",
"version": "1.14.3",
"license": "GPL-3.0",
"private": true,
"scripts": {
- "build": "tsc --build",
+ "lint": "eslint \"./src/**/*.ts\"",
+ "build": "npm run gen-docs && tsc --build",
"watch": "tsc --build --watch",
"clean": "tsc --build --clean",
- "start": "npm run build && node ./build/server.js",
+ "ts-check": "tsc --noEmit",
+ "start": "node ./dist/server.js",
"test": "vitest run",
"test-coverage": "vitest run --coverage",
- "dev": "ts-node-dev --transpile-only --inspect -- ./src/server.ts",
+ "dev": "concurrently -p none \"tsx watch --clear-screen=false --inspect ./src/server.ts\" \"tsc --preserveWatchOutput --noEmit --watch\" \"esw src/ -w --ext .ts --cache --color\"",
"knip": "knip",
"docker-db-only": "docker compose -f docker/compose.db-only.yml up",
- "docker": "docker compose -f docker/compose.yml up"
+ "docker": "docker compose -f docker/compose.yml up",
+ "gen-docs": "tsx scripts/openapi.ts dist/static/api/openapi.json && redocly build-docs -o dist/static/api/internal.html internal@v2 && redocly bundle -o dist/static/api/public.json public-filter && redocly build-docs -o dist/static/api/public.html public@v2"
},
"engines": {
- "node": "18.19.1",
- "npm": "10.2.4"
+ "node": "20.16.0"
},
"dependencies": {
"@date-fns/utc": "1.2.0",
+ "@monkeytype/contracts": "workspace:*",
+ "@ts-rest/core": "3.49.3",
+ "@ts-rest/express": "3.49.3",
+ "@ts-rest/open-api": "3.49.3",
"bcrypt": "5.1.1",
"bullmq": "1.91.1",
"chalk": "4.1.2",
"cors": "2.8.5",
"cron": "2.3.0",
"date-fns": "3.6.0",
- "dotenv": "10.0.0",
- "express": "4.17.3",
+ "dotenv": "16.4.5",
+ "express": "4.19.2",
"express-rate-limit": "6.2.1",
"firebase-admin": "12.0.0",
"helmet": "4.6.0",
@@ -40,49 +46,55 @@
"mongodb": "6.3.0",
"mustache": "4.2.0",
"node-fetch": "2.6.7",
- "nodemailer": "6.9.9",
- "nodemon": "3.0.1",
+ "nodemailer": "6.9.14",
+ "nodemon": "3.1.4",
"object-hash": "3.0.0",
"path": "0.12.7",
- "prom-client": "14.0.1",
- "rate-limiter-flexible": "2.3.7",
+ "prom-client": "15.1.3",
+ "rate-limiter-flexible": "5.0.3",
"simple-git": "3.16.0",
"string-similarity": "4.0.4",
- "swagger-stats": "0.99.5",
+ "swagger-stats": "0.99.7",
"swagger-ui-express": "4.3.0",
- "ua-parser-js": "0.7.28",
- "uuid": "9.0.1",
- "winston": "3.6.0"
+ "ua-parser-js": "0.7.33",
+ "uuid": "10.0.0",
+ "winston": "3.6.0",
+ "zod": "3.23.8"
},
"devDependencies": {
- "@types/bcrypt": "5.0.0",
+ "@monkeytype/eslint-config": "workspace:*",
+ "@monkeytype/shared-types": "workspace:*",
+ "@monkeytype/typescript-config": "workspace:*",
+ "@redocly/cli": "1.19.0",
+ "@types/bcrypt": "5.0.2",
"@types/cors": "2.8.12",
"@types/cron": "1.7.3",
"@types/express": "4.17.21",
"@types/ioredis": "4.28.10",
"@types/lodash": "4.14.178",
+ "@types/mjml": "4.7.4",
"@types/mustache": "4.2.2",
- "@types/node": "18.19.1",
+ "@types/node": "20.14.11",
"@types/node-fetch": "2.6.1",
- "@types/nodemailer": "6.4.7",
- "@types/object-hash": "2.2.1",
+ "@types/nodemailer": "6.4.15",
+ "@types/object-hash": "3.0.6",
"@types/readline-sync": "1.4.8",
- "@types/string-similarity": "4.0.0",
+ "@types/string-similarity": "4.0.2",
"@types/supertest": "2.0.12",
- "@types/swagger-stats": "0.95.4",
+ "@types/swagger-stats": "0.95.11",
"@types/swagger-ui-express": "4.1.3",
"@types/ua-parser-js": "0.7.36",
- "@types/uuid": "8.3.4",
- "@vitest/coverage-v8": "1.6.0",
+ "@types/uuid": "10.0.0",
+ "@vitest/coverage-v8": "2.0.5",
+ "concurrently": "8.2.2",
+ "eslint": "8.57.0",
+ "eslint-watch": "8.0.0",
"ioredis-mock": "7.4.0",
"readline-sync": "1.4.10",
"supertest": "6.2.3",
- "ts-node-dev": "2.0.0",
- "typescript": "5.3.3",
- "vitest": "1.6.0",
- "vitest-mongodb": "0.0.5"
- },
- "overrides": {
- "mongodb-memory-server": "9.2.0"
+ "tsx": "4.16.2",
+ "typescript": "5.5.4",
+ "vitest": "2.0.5",
+ "vitest-mongodb": "1.0.0"
}
}
diff --git a/backend/redocly.yaml b/backend/redocly.yaml
new file mode 100644
index 000000000000..7bacf646af47
--- /dev/null
+++ b/backend/redocly.yaml
@@ -0,0 +1,46 @@
+extends:
+ - recommended
+
+apis:
+ internal@v2:
+ root: dist/static/api/openapi.json
+ public-filter:
+ root: dist/static/api/openapi.json
+ decorators:
+ filter-in:
+ property: x-public
+ value: yes
+ public@v2:
+ root: dist/static/api/public.json
+
+features.openapi:
+ theme:
+ logo:
+ gutter: "2rem"
+ colors:
+ primary:
+ main: "#e2b714"
+ border:
+ dark: "#e2b714"
+ light: "#e2b714"
+ error:
+ main: "#da3333"
+ success:
+ main: "#009400"
+ text:
+ primary: "#646669"
+ secondary: "#d1d0c5"
+ warning:
+ main: "#FF00FF"
+ http:
+ delete: "#da3333"
+ post: "#004D94"
+ patch: "#e2b714"
+ get: "#009400"
+ sidebar:
+ backgroundColor: "#323437"
+ textColor: "#d1d0c5"
+ activeTextColor: "#e2b714"
+ rightPanel:
+ backgroundColor: "#323437"
+ textColor: "#d1d0c5"
diff --git a/backend/scripts/openapi.ts b/backend/scripts/openapi.ts
new file mode 100644
index 000000000000..02b814e2f157
--- /dev/null
+++ b/backend/scripts/openapi.ts
@@ -0,0 +1,176 @@
+import { generateOpenApi } from "@ts-rest/open-api";
+import { contract } from "@monkeytype/contracts/index";
+import { writeFileSync, mkdirSync } from "fs";
+import { EndpointMetadata } from "@monkeytype/contracts/schemas/api";
+import type { OpenAPIObject } from "openapi3-ts";
+
+type SecurityRequirementObject = {
+ [name: string]: string[];
+};
+
+export function getOpenApi(): OpenAPIObject {
+ const openApiDocument = generateOpenApi(
+ contract,
+ {
+ openapi: "3.1.0",
+ info: {
+ title: "Monkeytype API",
+ description:
+ "Documentation for the public endpoints provided by the Monkeytype API server.\n\nNote that authentication is performed with the Authorization HTTP header in the format `Authorization: ApeKey YOUR_APE_KEY`\n\nThere is a rate limit of `30 requests per minute` across all endpoints with some endpoints being more strict. Rate limit rates are shared across all ape keys.",
+ version: "2.0.0",
+ termsOfService: "https://monkeytype.com/terms-of-service",
+ contact: {
+ name: "Support",
+ email: "support@monkeytype.com",
+ },
+ "x-logo": {
+ url: "https://monkeytype.com/images/mtfulllogo.png",
+ },
+ license: {
+ name: "GPL-3.0",
+ url: "https://www.gnu.org/licenses/gpl-3.0.html",
+ },
+ },
+ servers: [
+ {
+ url: "https://api.monkeytype.com",
+ description: "Production server",
+ },
+ ],
+ components: {
+ securitySchemes: {
+ BearerAuth: {
+ type: "http",
+ scheme: "bearer",
+ },
+ ApeKey: {
+ type: "http",
+ scheme: "ApeKey",
+ },
+ },
+ },
+ tags: [
+ {
+ name: "configs",
+ description:
+ "User specific configs like test settings, theme or tags.",
+ "x-displayName": "User configs",
+ "x-public": "no",
+ },
+ {
+ name: "presets",
+ description: "User specific configuration presets.",
+ "x-displayName": "User presets",
+ "x-public": "no",
+ },
+ {
+ name: "results",
+ description: "User test results",
+ "x-displayName": "Test results",
+ "x-public": "yes",
+ },
+ {
+ name: "ape-keys",
+ description: "Ape keys provide access to certain API endpoints.",
+ "x-displayName": "Ape Keys",
+ "x-public": "no",
+ },
+ {
+ name: "public",
+ description: "Public endpoints such as typing stats.",
+ "x-displayName": "Public",
+ "x-public": "yes",
+ },
+ {
+ name: "leaderboards",
+ description: "All-time and daily leaderboards of the fastest typers.",
+ "x-displayName": "Leaderboards",
+ },
+ {
+ name: "psas",
+ description: "Public service announcements.",
+ "x-displayName": "PSAs",
+ "x-public": "yes",
+ },
+ {
+ name: "admin",
+ description:
+ "Various administrative endpoints. Require user to have admin permissions.",
+ "x-displayName": "Admin",
+ "x-public": "no",
+ },
+ {
+ name: "configuration",
+ description: "Server configuration",
+ "x-displayName": "Server configuration",
+ "x-public": "yes",
+ },
+ {
+ name: "dev",
+ description:
+ "Development related endpoints. Only available on dev environment",
+ "x-displayName": "Development",
+ "x-public": "no",
+ },
+ ],
+ },
+
+ {
+ jsonQuery: true,
+ setOperationId: "concatenated-path",
+ operationMapper: (operation, route) => ({
+ ...operation,
+ ...addAuth(route.metadata as EndpointMetadata),
+ ...addTags(route.metadata as EndpointMetadata),
+ }),
+ }
+ );
+ return openApiDocument;
+}
+
+function addAuth(metadata: EndpointMetadata | undefined): object {
+ const auth = metadata?.["authenticationOptions"] ?? {};
+ const security: SecurityRequirementObject[] = [];
+ if (!auth.isPublic === true) {
+ security.push({ BearerAuth: [] });
+
+ if (auth.acceptApeKeys === true) {
+ security.push({ ApeKey: [] });
+ }
+ }
+
+ const includeInPublic = auth.isPublic === true || auth.acceptApeKeys === true;
+ return {
+ "x-public": includeInPublic ? "yes" : "no",
+ security,
+ };
+}
+
+function addTags(metadata: EndpointMetadata | undefined): object {
+ if (metadata === undefined || metadata.openApiTags === undefined) return {};
+ return {
+ tags: Array.isArray(metadata.openApiTags)
+ ? metadata.openApiTags
+ : [metadata.openApiTags],
+ };
+}
+
+//detect if we run this as a main
+if (require.main === module) {
+ const args = process.argv.slice(2);
+ if (args.length !== 1) {
+ console.error("Provide filename.");
+ process.exit(1);
+ }
+ const outFile = args[0] as string;
+
+ //create directories if needed
+ const lastSlash = outFile.lastIndexOf("/");
+ if (lastSlash > 1) {
+ const dir = outFile.substring(0, lastSlash);
+ mkdirSync(dir, { recursive: true });
+ }
+
+ const openapi = getOpenApi();
+ writeFileSync(args[0] as string, JSON.stringify(openapi, null, 2));
+}
diff --git a/backend/src/anticheat/index.ts b/backend/src/anticheat/index.ts
index 51f17d15cee7..57c52aa34a31 100644
--- a/backend/src/anticheat/index.ts
+++ b/backend/src/anticheat/index.ts
@@ -1,3 +1,8 @@
+import {
+ CompletedEvent,
+ KeyStats,
+} from "@monkeytype/contracts/schemas/results";
+
export function implemented(): boolean {
return false;
}
@@ -11,6 +16,11 @@ export function validateResult(
return true;
}
-export function validateKeys(_result: object, _uid: string): boolean {
+export function validateKeys(
+ _result: CompletedEvent,
+ _keySpacingStats: KeyStats,
+ _keyDurationStats: KeyStats,
+ _uid: string
+): boolean {
return true;
}
diff --git a/backend/src/api/controllers/admin.ts b/backend/src/api/controllers/admin.ts
index da8e66f63fb7..8779efe9c884 100644
--- a/backend/src/api/controllers/admin.ts
+++ b/backend/src/api/controllers/admin.ts
@@ -1,29 +1,75 @@
-import { MonkeyResponse } from "../../utils/monkey-response";
+import { MonkeyResponse2 } from "../../utils/monkey-response";
import { buildMonkeyMail } from "../../utils/monkey-mail";
import * as UserDAL from "../../dal/user";
import * as ReportDAL from "../../dal/report";
+import GeorgeQueue from "../../queues/george-queue";
+import { sendForgotPasswordEmail as authSendForgotPasswordEmail } from "../../utils/auth";
+import {
+ AcceptReportsRequest,
+ RejectReportsRequest,
+ SendForgotPasswordEmailRequest,
+ ToggleBanRequest,
+ ToggleBanResponse,
+} from "@monkeytype/contracts/admin";
+import MonkeyError from "../../utils/error";
+import { Configuration } from "@monkeytype/contracts/schemas/configuration";
+import { addImportantLog } from "../../dal/logs";
-export async function test(): Promise {
- return new MonkeyResponse("OK");
+export async function test(
+ _req: MonkeyTypes.Request2
+): Promise {
+ return new MonkeyResponse2("OK", null);
+}
+
+export async function toggleBan(
+ req: MonkeyTypes.Request2
+): Promise {
+ const { uid } = req.body;
+
+ const user = await UserDAL.getPartialUser(uid, "toggle ban", [
+ "banned",
+ "discordId",
+ ]);
+ const discordId = user.discordId;
+ const discordIdIsValid = discordId !== undefined && discordId !== "";
+
+ await UserDAL.setBanned(uid, !user.banned);
+ if (discordIdIsValid) await GeorgeQueue.userBanned(discordId, !user.banned);
+
+ void addImportantLog("user_ban_toggled", { banned: !user.banned }, uid);
+
+ return new MonkeyResponse2(`Ban toggled`, {
+ banned: !user.banned,
+ });
}
export async function acceptReports(
- req: MonkeyTypes.Request
-): Promise {
- return handleReports(req, true);
+ req: MonkeyTypes.Request2
+): Promise {
+ await handleReports(
+ req.body.reports.map((it) => ({ ...it })),
+ true,
+ req.ctx.configuration.users.inbox
+ );
+ return new MonkeyResponse2("Reports removed and users notified.", null);
}
export async function rejectReports(
- req: MonkeyTypes.Request
-): Promise {
- return handleReports(req, false);
+ req: MonkeyTypes.Request2
+): Promise {
+ await handleReports(
+ req.body.reports.map((it) => ({ ...it })),
+ false,
+ req.ctx.configuration.users.inbox
+ );
+ return new MonkeyResponse2("Reports removed and users notified.", null);
}
export async function handleReports(
- req: MonkeyTypes.Request,
- accept: boolean
-): Promise {
- const { reports } = req.body;
+ reports: { reportId: string; reason?: string }[],
+ accept: boolean,
+ inboxConfig: Configuration["users"]["inbox"]
+): Promise {
const reportIds = reports.map(({ reportId }) => reportId);
const reportsFromDb = await ReportDAL.getReports(reportIds);
@@ -35,10 +81,9 @@ export async function handleReports(
);
if (missingReportIds.length > 0) {
- return new MonkeyResponse(
- `Reports not found for some IDs`,
- missingReportIds,
- 404
+ throw new MonkeyError(
+ 404,
+ `Reports not found for some IDs ${missingReportIds.join(",")}`
);
}
@@ -48,11 +93,7 @@ export async function handleReports(
try {
const report = reportById.get(reportId);
if (!report) {
- return new MonkeyResponse(
- `Report not found for ID: ${reportId}`,
- null,
- 404
- );
+ throw new MonkeyError(404, `Report not found for ID: ${reportId}`);
}
let mailBody = "";
@@ -73,14 +114,17 @@ export async function handleReports(
subject: mailSubject,
body: mailBody,
});
- await UserDAL.addToInbox(
- report.uid,
- [mail],
- req.ctx.configuration.users.inbox
- );
+ await UserDAL.addToInbox(report.uid, [mail], inboxConfig);
} catch (e) {
- return new MonkeyResponse(e.message, null, e.status);
+ throw new MonkeyError(e.status, e.message);
}
}
- return new MonkeyResponse("Reports removed and users notified.");
+}
+
+export async function sendForgotPasswordEmail(
+ req: MonkeyTypes.Request2
+): Promise {
+ const { email } = req.body;
+ await authSendForgotPasswordEmail(email);
+ return new MonkeyResponse2("Password reset request email sent.", null);
}
diff --git a/backend/src/api/controllers/ape-key.ts b/backend/src/api/controllers/ape-key.ts
index 85e21871c255..6ee02822cdef 100644
--- a/backend/src/api/controllers/ape-key.ts
+++ b/backend/src/api/controllers/ape-key.ts
@@ -3,28 +3,37 @@ import { randomBytes } from "crypto";
import { hash } from "bcrypt";
import * as ApeKeysDAL from "../../dal/ape-keys";
import MonkeyError from "../../utils/error";
-import { MonkeyResponse } from "../../utils/monkey-response";
+import { MonkeyResponse2 } from "../../utils/monkey-response";
import { base64UrlEncode } from "../../utils/misc";
import { ObjectId } from "mongodb";
-function cleanApeKey(apeKey: MonkeyTypes.ApeKeyDB): SharedTypes.ApeKey {
+import {
+ AddApeKeyRequest,
+ AddApeKeyResponse,
+ ApeKeyParams,
+ EditApeKeyRequest,
+ GetApeKeyResponse,
+} from "@monkeytype/contracts/ape-keys";
+import { ApeKey } from "@monkeytype/contracts/schemas/ape-keys";
+
+function cleanApeKey(apeKey: MonkeyTypes.ApeKeyDB): ApeKey {
return _.omit(apeKey, "hash", "_id", "uid", "useCount");
}
export async function getApeKeys(
- req: MonkeyTypes.Request
-): Promise {
+ req: MonkeyTypes.Request2
+): Promise {
const { uid } = req.ctx.decodedToken;
const apeKeys = await ApeKeysDAL.getApeKeys(uid);
const cleanedKeys = _(apeKeys).keyBy("_id").mapValues(cleanApeKey).value();
- return new MonkeyResponse("ApeKeys retrieved", cleanedKeys);
+ return new MonkeyResponse2("ApeKeys retrieved", cleanedKeys);
}
export async function generateApeKey(
- req: MonkeyTypes.Request
-): Promise {
+ req: MonkeyTypes.Request2
+): Promise {
const { name, enabled } = req.body;
const { uid } = req.ctx.decodedToken;
const { maxKeysPerUser, apeKeyBytes, apeKeySaltRounds } =
@@ -53,7 +62,7 @@ export async function generateApeKey(
const apeKeyId = await ApeKeysDAL.addApeKey(apeKey);
- return new MonkeyResponse("ApeKey generated", {
+ return new MonkeyResponse2("ApeKey generated", {
apeKey: base64UrlEncode(`${apeKeyId}.${apiKey}`),
apeKeyId,
apeKeyDetails: cleanApeKey(apeKey),
@@ -61,24 +70,24 @@ export async function generateApeKey(
}
export async function editApeKey(
- req: MonkeyTypes.Request
-): Promise {
+ req: MonkeyTypes.Request2
+): Promise {
const { apeKeyId } = req.params;
const { name, enabled } = req.body;
const { uid } = req.ctx.decodedToken;
- await ApeKeysDAL.editApeKey(uid, apeKeyId as string, name, enabled);
+ await ApeKeysDAL.editApeKey(uid, apeKeyId, name, enabled);
- return new MonkeyResponse("ApeKey updated");
+ return new MonkeyResponse2("ApeKey updated", null);
}
export async function deleteApeKey(
- req: MonkeyTypes.Request
-): Promise {
+ req: MonkeyTypes.Request2
+): Promise {
const { apeKeyId } = req.params;
const { uid } = req.ctx.decodedToken;
- await ApeKeysDAL.deleteApeKey(uid, apeKeyId as string);
+ await ApeKeysDAL.deleteApeKey(uid, apeKeyId);
- return new MonkeyResponse("ApeKey deleted");
+ return new MonkeyResponse2("ApeKey deleted", null);
}
diff --git a/backend/src/api/controllers/config.ts b/backend/src/api/controllers/config.ts
index bb93d7c9cbad..7ba725ed9c0e 100644
--- a/backend/src/api/controllers/config.ts
+++ b/backend/src/api/controllers/config.ts
@@ -1,22 +1,33 @@
+import { PartialConfig } from "@monkeytype/contracts/schemas/configs";
import * as ConfigDAL from "../../dal/config";
-import { MonkeyResponse } from "../../utils/monkey-response";
+import { MonkeyResponse2 } from "../../utils/monkey-response";
+import { GetConfigResponse } from "@monkeytype/contracts/configs";
export async function getConfig(
- req: MonkeyTypes.Request
-): Promise {
+ req: MonkeyTypes.Request2
+): Promise {
const { uid } = req.ctx.decodedToken;
+ const data = (await ConfigDAL.getConfig(uid))?.config ?? null;
- const data = await ConfigDAL.getConfig(uid);
- return new MonkeyResponse("Configuration retrieved", data);
+ return new MonkeyResponse2("Configuration retrieved", data);
}
export async function saveConfig(
- req: MonkeyTypes.Request
-): Promise {
- const { config } = req.body;
+ req: MonkeyTypes.Request2
+): Promise {
+ const config = req.body;
const { uid } = req.ctx.decodedToken;
await ConfigDAL.saveConfig(uid, config);
- return new MonkeyResponse("Config updated");
+ return new MonkeyResponse2("Config updated", null);
+}
+
+export async function deleteConfig(
+ req: MonkeyTypes.Request2
+): Promise {
+ const { uid } = req.ctx.decodedToken;
+
+ await ConfigDAL.deleteConfig(uid);
+ return new MonkeyResponse2("Config deleted", null);
}
diff --git a/backend/src/api/controllers/configuration.ts b/backend/src/api/controllers/configuration.ts
index df99007077b1..6a76ab210690 100644
--- a/backend/src/api/controllers/configuration.ts
+++ b/backend/src/api/controllers/configuration.ts
@@ -1,32 +1,38 @@
import * as Configuration from "../../init/configuration";
-import { MonkeyResponse } from "../../utils/monkey-response";
+import { MonkeyResponse2 } from "../../utils/monkey-response";
import { CONFIGURATION_FORM_SCHEMA } from "../../constants/base-configuration";
+import {
+ ConfigurationSchemaResponse,
+ GetConfigurationResponse,
+ PatchConfigurationRequest,
+} from "@monkeytype/contracts/configuration";
+import MonkeyError from "../../utils/error";
export async function getConfiguration(
- _req: MonkeyTypes.Request
-): Promise {
+ _req: MonkeyTypes.Request2
+): Promise {
const currentConfiguration = await Configuration.getLiveConfiguration();
- return new MonkeyResponse("Configuration retrieved", currentConfiguration);
+ return new MonkeyResponse2("Configuration retrieved", currentConfiguration);
}
export async function getSchema(
- _req: MonkeyTypes.Request
-): Promise {
- return new MonkeyResponse(
+ _req: MonkeyTypes.Request2
+): Promise {
+ return new MonkeyResponse2(
"Configuration schema retrieved",
CONFIGURATION_FORM_SCHEMA
);
}
export async function updateConfiguration(
- req: MonkeyTypes.Request
-): Promise {
+ req: MonkeyTypes.Request2
+): Promise {
const { configuration } = req.body;
const success = await Configuration.patchConfiguration(configuration);
if (!success) {
- return new MonkeyResponse("Configuration update failed", {}, 500);
+ throw new MonkeyError(500, "Configuration update failed");
}
- return new MonkeyResponse("Configuration updated");
+ return new MonkeyResponse2("Configuration updated", null);
}
diff --git a/backend/src/api/controllers/dev.ts b/backend/src/api/controllers/dev.ts
index 122f24018fbe..e380cfc9d671 100644
--- a/backend/src/api/controllers/dev.ts
+++ b/backend/src/api/controllers/dev.ts
@@ -1,4 +1,4 @@
-import { MonkeyResponse } from "../../utils/monkey-response";
+import { MonkeyResponse2 } from "../../utils/monkey-response";
import * as UserDal from "../../dal/user";
import FirebaseAdmin from "../../init/firebase-admin";
import Logger from "../../utils/logger";
@@ -8,26 +8,28 @@ import * as ResultDal from "../../dal/result";
import { roundTo2 } from "../../utils/misc";
import { ObjectId } from "mongodb";
import * as LeaderboardDal from "../../dal/leaderboards";
-import { isNumber } from "lodash";
import MonkeyError from "../../utils/error";
-type GenerateDataOptions = {
- firstTestTimestamp: Date;
- lastTestTimestamp: Date;
- minTestsPerDay: number;
- maxTestsPerDay: number;
-};
-
-const CREATE_RESULT_DEFAULT_OPTIONS: GenerateDataOptions = {
- firstTestTimestamp: DateUtils.startOfDay(new UTCDate(Date.now())),
- lastTestTimestamp: DateUtils.endOfDay(new UTCDate(Date.now())),
+import {
+ Mode,
+ PersonalBest,
+ PersonalBests,
+} from "@monkeytype/contracts/schemas/shared";
+import {
+ GenerateDataRequest,
+ GenerateDataResponse,
+} from "@monkeytype/contracts/dev";
+
+const CREATE_RESULT_DEFAULT_OPTIONS = {
+ firstTestTimestamp: DateUtils.startOfDay(new UTCDate(Date.now())).valueOf(),
+ lastTestTimestamp: DateUtils.endOfDay(new UTCDate(Date.now())).valueOf(),
minTestsPerDay: 0,
maxTestsPerDay: 50,
};
export async function createTestData(
- req: MonkeyTypes.Request
-): Promise {
+ req: MonkeyTypes.Request2
+): Promise {
const { username, createUser } = req.body;
const user = await getOrCreateUser(username, "password", createUser);
@@ -37,7 +39,7 @@ export async function createTestData(
await updateUser(uid);
await updateLeaderboard();
- return new MonkeyResponse("test data created", { uid, email }, 200);
+ return new MonkeyResponse2("test data created", { uid, email });
}
async function getOrCreateUser(
@@ -49,7 +51,7 @@ async function getOrCreateUser(
if (existingUser !== undefined && existingUser !== null) {
return existingUser;
- } else if (createUser === false) {
+ } else if (!createUser) {
throw new MonkeyError(404, `User ${username} does not exist.`);
}
@@ -68,20 +70,18 @@ async function getOrCreateUser(
async function createTestResults(
user: MonkeyTypes.DBUser,
- configOptions: Partial
+ configOptions: GenerateDataRequest
): Promise {
const config = {
...CREATE_RESULT_DEFAULT_OPTIONS,
...configOptions,
};
- if (isNumber(config.firstTestTimestamp))
- config.firstTestTimestamp = toDate(config.firstTestTimestamp);
- if (isNumber(config.lastTestTimestamp))
- config.lastTestTimestamp = toDate(config.lastTestTimestamp);
+ const start = toDate(config.firstTestTimestamp);
+ const end = toDate(config.lastTestTimestamp);
const days = DateUtils.eachDayOfInterval({
- start: config.firstTestTimestamp,
- end: config.lastTestTimestamp,
+ start,
+ end,
}).map((day) => ({
timestamp: DateUtils.startOfDay(day),
amount: Math.round(random(config.minTestsPerDay, config.maxTestsPerDay)),
@@ -113,7 +113,7 @@ function createResult(
user: MonkeyTypes.DBUser,
timestamp: Date //evil, we modify this value
): MonkeyTypes.DBResult {
- const mode: SharedTypes.Config.Mode = randomValue(["time", "words"]);
+ const mode: Mode = randomValue(["time", "words"]);
const mode2: number =
mode === "time"
? randomValue([15, 30, 60, 120])
@@ -129,7 +129,7 @@ function createResult(
charStats: [131, 0, 0, 0],
acc: random(80, 100),
language: "english",
- mode: mode as SharedTypes.Config.Mode,
+ mode: mode as Mode,
mode2: mode2 as unknown as never,
timestamp: timestamp.valueOf(),
testDuration: testDuration,
@@ -180,8 +180,11 @@ async function updateUser(uid: string): Promise {
])
.toArray();
- const timeTyping = stats.reduce((a, c) => a + c["timeTyping"], 0);
- const completedTests = stats.reduce((a, c) => a + c["completedTests"], 0);
+ const timeTyping = stats.reduce((a, c) => (a + c["timeTyping"]) as number, 0);
+ const completedTests = stats.reduce(
+ (a, c) => (a + c["completedTests"]) as number,
+ 0
+ );
//update PBs
const lbPersonalBests: MonkeyTypes.LbPersonalBests = {
@@ -191,14 +194,22 @@ async function updateUser(uid: string): Promise {
},
};
- const personalBests: SharedTypes.PersonalBests = {
+ const personalBests: PersonalBests = {
time: {},
custom: {},
words: {},
zen: {},
quote: {},
};
- const modes = stats.map((it) => it["_id"]);
+ const modes = stats.map(
+ (it) =>
+ it["_id"] as {
+ language: string;
+ mode: "time" | "custom" | "words" | "quote" | "zen";
+ mode2: `${number}` | "custom" | "zen";
+ }
+ );
+
for (const mode of modes) {
const best = (
await ResultDal.getResultCollection()
@@ -228,7 +239,7 @@ async function updateUser(uid: string): Promise {
wpm: best.wpm,
numbers: best.numbers,
timestamp: best.timestamp,
- } as SharedTypes.PersonalBest;
+ } as PersonalBest;
personalBests[mode.mode][mode.mode2].push(entry);
@@ -251,7 +262,7 @@ async function updateUser(uid: string): Promise {
timeTyping: timeTyping,
completedTests: completedTests,
startedTests: Math.round(completedTests * 1.25),
- personalBests: personalBests as SharedTypes.PersonalBests,
+ personalBests: personalBests,
lbPersonalBests: lbPersonalBests,
},
}
diff --git a/backend/src/api/controllers/leaderboard.ts b/backend/src/api/controllers/leaderboard.ts
index bed10e239727..b06439250bd6 100644
--- a/backend/src/api/controllers/leaderboard.ts
+++ b/backend/src/api/controllers/leaderboard.ts
@@ -4,85 +4,81 @@ import {
MILLISECONDS_IN_DAY,
getCurrentWeekTimestamp,
} from "../../utils/misc";
-import { MonkeyResponse } from "../../utils/monkey-response";
+import { MonkeyResponse2 } from "../../utils/monkey-response";
import * as LeaderboardsDAL from "../../dal/leaderboards";
import MonkeyError from "../../utils/error";
import * as DailyLeaderboards from "../../utils/daily-leaderboards";
import * as WeeklyXpLeaderboard from "../../services/weekly-xp-leaderboard";
+import {
+ GetDailyLeaderboardQuery,
+ GetDailyLeaderboardRankQuery,
+ GetLeaderboardDailyRankResponse,
+ GetLeaderboardQuery,
+ GetLeaderboardRankResponse,
+ GetLeaderboardResponse as GetLeaderboardResponse,
+ GetWeeklyXpLeaderboardQuery,
+ GetWeeklyXpLeaderboardRankResponse,
+ GetWeeklyXpLeaderboardResponse,
+ LanguageAndModeQuery,
+} from "@monkeytype/contracts/leaderboards";
+import { Configuration } from "@monkeytype/contracts/schemas/configuration";
export async function getLeaderboard(
- req: MonkeyTypes.Request
-): Promise {
- const { language, mode, mode2, skip, limit = 50 } = req.query;
- const { uid } = req.ctx.decodedToken;
-
- const queryLimit = Math.min(parseInt(limit as string, 10), 50);
+ req: MonkeyTypes.Request2
+): Promise {
+ const { language, mode, mode2, skip = 0, limit = 50 } = req.query;
const leaderboard = await LeaderboardsDAL.get(
- mode as string,
- mode2 as string,
- language as string,
- parseInt(skip as string, 10),
- queryLimit
+ mode,
+ mode2,
+ language,
+ skip,
+ limit
);
if (leaderboard === false) {
- return new MonkeyResponse(
- "Leaderboard is currently updating. Please try again in a few seconds.",
- null,
- 503
+ throw new MonkeyError(
+ 503,
+ "Leaderboard is currently updating. Please try again in a few seconds."
);
}
- const normalizedLeaderboard = _.map(leaderboard, (entry) => {
- return uid && entry.uid === uid
- ? entry
- : _.omit(entry, ["_id", "difficulty", "language"]);
- });
+ const normalizedLeaderboard = leaderboard.map((it) => _.omit(it, ["_id"]));
- return new MonkeyResponse("Leaderboard retrieved", normalizedLeaderboard);
+ return new MonkeyResponse2("Leaderboard retrieved", normalizedLeaderboard);
}
export async function getRankFromLeaderboard(
- req: MonkeyTypes.Request
-): Promise {
+ req: MonkeyTypes.Request2
+): Promise {
const { language, mode, mode2 } = req.query;
const { uid } = req.ctx.decodedToken;
- const data = await LeaderboardsDAL.getRank(
- mode as string,
- mode2 as string,
- language as string,
- uid
- );
+ const data = await LeaderboardsDAL.getRank(mode, mode2, language, uid);
if (data === false) {
- return new MonkeyResponse(
- "Leaderboard is currently updating. Please try again in a few seconds.",
- null,
- 503
+ throw new MonkeyError(
+ 503,
+ "Leaderboard is currently updating. Please try again in a few seconds."
);
}
- return new MonkeyResponse("Rank retrieved", data);
+ return new MonkeyResponse2("Rank retrieved", data);
}
function getDailyLeaderboardWithError(
- req: MonkeyTypes.Request
+ { language, mode, mode2, daysBefore }: GetDailyLeaderboardRankQuery,
+ config: Configuration["dailyLeaderboards"]
): DailyLeaderboards.DailyLeaderboard {
- const { language, mode, mode2, daysBefore } = req.query;
-
- const normalizedDayBefore = parseInt(daysBefore as string, 10);
- const currentDayTimestamp = getCurrentDayTimestamp();
- const dayBeforeTimestamp =
- currentDayTimestamp - normalizedDayBefore * MILLISECONDS_IN_DAY;
-
- const customTimestamp = _.isNil(daysBefore) ? -1 : dayBeforeTimestamp;
+ const customTimestamp =
+ daysBefore === undefined
+ ? -1
+ : getCurrentDayTimestamp() - daysBefore * MILLISECONDS_IN_DAY;
const dailyLeaderboard = DailyLeaderboards.getDailyLeaderboard(
- language as string,
- mode as string,
- mode2 as string,
- req.ctx.configuration.dailyLeaderboards,
+ language,
+ mode,
+ mode2,
+ config,
customTimestamp
);
if (!dailyLeaderboard) {
@@ -93,14 +89,17 @@ function getDailyLeaderboardWithError(
}
export async function getDailyLeaderboard(
- req: MonkeyTypes.Request
-): Promise {
+ req: MonkeyTypes.Request2
+): Promise {
const { skip = 0, limit = 50 } = req.query;
- const dailyLeaderboard = getDailyLeaderboardWithError(req);
+ const dailyLeaderboard = getDailyLeaderboardWithError(
+ req.query,
+ req.ctx.configuration.dailyLeaderboards
+ );
- const minRank = parseInt(skip as string, 10);
- const maxRank = minRank + parseInt(limit as string, 10) - 1;
+ const minRank = skip;
+ const maxRank = minRank + limit - 1;
const topResults = await dailyLeaderboard.getResults(
minRank,
@@ -109,40 +108,37 @@ export async function getDailyLeaderboard(
req.ctx.configuration.users.premium.enabled
);
- return new MonkeyResponse("Daily leaderboard retrieved", topResults);
+ return new MonkeyResponse2("Daily leaderboard retrieved", topResults);
}
export async function getDailyLeaderboardRank(
- req: MonkeyTypes.Request
-): Promise {
+ req: MonkeyTypes.Request2
+): Promise {
const { uid } = req.ctx.decodedToken;
- const dailyLeaderboard = getDailyLeaderboardWithError(req);
+ const dailyLeaderboard = getDailyLeaderboardWithError(
+ req.query,
+ req.ctx.configuration.dailyLeaderboards
+ );
const rank = await dailyLeaderboard.getRank(
uid,
req.ctx.configuration.dailyLeaderboards
);
- return new MonkeyResponse("Daily leaderboard rank retrieved", rank);
+ return new MonkeyResponse2("Daily leaderboard rank retrieved", rank);
}
function getWeeklyXpLeaderboardWithError(
- req: MonkeyTypes.Request
+ { weeksBefore }: GetWeeklyXpLeaderboardQuery,
+ config: Configuration["leaderboards"]["weeklyXp"]
): WeeklyXpLeaderboard.WeeklyXpLeaderboard {
- const { weeksBefore } = req.query;
-
- const normalizedWeeksBefore = parseInt(weeksBefore as string, 10);
- const currentWeekTimestamp = getCurrentWeekTimestamp();
- const weekBeforeTimestamp =
- currentWeekTimestamp - normalizedWeeksBefore * MILLISECONDS_IN_DAY * 7;
-
- const customTimestamp = _.isNil(weeksBefore) ? -1 : weekBeforeTimestamp;
+ const customTimestamp =
+ weeksBefore === undefined
+ ? -1
+ : getCurrentWeekTimestamp() - weeksBefore * MILLISECONDS_IN_DAY * 7;
- const weeklyXpLeaderboard = WeeklyXpLeaderboard.get(
- req.ctx.configuration.leaderboards.weeklyXp,
- customTimestamp
- );
+ const weeklyXpLeaderboard = WeeklyXpLeaderboard.get(config, customTimestamp);
if (!weeklyXpLeaderboard) {
throw new MonkeyError(404, "XP leaderboard for this week not found.");
}
@@ -151,33 +147,39 @@ function getWeeklyXpLeaderboardWithError(
}
export async function getWeeklyXpLeaderboardResults(
- req: MonkeyTypes.Request
-): Promise {
+ req: MonkeyTypes.Request2
+): Promise {
const { skip = 0, limit = 50 } = req.query;
- const minRank = parseInt(skip as string, 10);
- const maxRank = minRank + parseInt(limit as string, 10) - 1;
+ const minRank = skip;
+ const maxRank = minRank + limit - 1;
- const weeklyXpLeaderboard = getWeeklyXpLeaderboardWithError(req);
+ const weeklyXpLeaderboard = getWeeklyXpLeaderboardWithError(
+ req.query,
+ req.ctx.configuration.leaderboards.weeklyXp
+ );
const results = await weeklyXpLeaderboard.getResults(
minRank,
maxRank,
req.ctx.configuration.leaderboards.weeklyXp
);
- return new MonkeyResponse("Weekly xp leaderboard retrieved", results);
+ return new MonkeyResponse2("Weekly xp leaderboard retrieved", results);
}
export async function getWeeklyXpLeaderboardRank(
- req: MonkeyTypes.Request
-): Promise {
+ req: MonkeyTypes.Request2
+): Promise {
const { uid } = req.ctx.decodedToken;
- const weeklyXpLeaderboard = getWeeklyXpLeaderboardWithError(req);
+ const weeklyXpLeaderboard = getWeeklyXpLeaderboardWithError(
+ {},
+ req.ctx.configuration.leaderboards.weeklyXp
+ );
const rankEntry = await weeklyXpLeaderboard.getRank(
uid,
req.ctx.configuration.leaderboards.weeklyXp
);
- return new MonkeyResponse("Weekly xp leaderboard rank retrieved", rankEntry);
+ return new MonkeyResponse2("Weekly xp leaderboard rank retrieved", rankEntry);
}
diff --git a/backend/src/api/controllers/preset.ts b/backend/src/api/controllers/preset.ts
index 0bf4e0e2d504..2253cc235f74 100644
--- a/backend/src/api/controllers/preset.ts
+++ b/backend/src/api/controllers/preset.ts
@@ -1,44 +1,56 @@
+import {
+ AddPresetRequest,
+ AddPresetResponse,
+ DeletePresetsParams,
+ GetPresetResponse,
+} from "@monkeytype/contracts/presets";
import * as PresetDAL from "../../dal/preset";
-import { MonkeyResponse } from "../../utils/monkey-response";
+import { MonkeyResponse2 } from "../../utils/monkey-response";
+import { replaceObjectId } from "../../utils/misc";
+import { Preset } from "@monkeytype/contracts/schemas/presets";
export async function getPresets(
- req: MonkeyTypes.Request
-): Promise {
+ req: MonkeyTypes.Request2
+): Promise {
const { uid } = req.ctx.decodedToken;
- const data = await PresetDAL.getPresets(uid);
- return new MonkeyResponse("Preset retrieved", data);
+ const data = (await PresetDAL.getPresets(uid))
+ .map((preset) => ({
+ ...preset,
+ uid: undefined,
+ }))
+ .map(replaceObjectId);
+
+ return new MonkeyResponse2("Presets retrieved", data);
}
export async function addPreset(
- req: MonkeyTypes.Request
-): Promise {
- const { name, config } = req.body;
+ req: MonkeyTypes.Request2
+): Promise {
const { uid } = req.ctx.decodedToken;
- const data = await PresetDAL.addPreset(uid, name, config);
+ const data = await PresetDAL.addPreset(uid, req.body);
- return new MonkeyResponse("Preset created", data);
+ return new MonkeyResponse2("Preset created", data);
}
export async function editPreset(
- req: MonkeyTypes.Request
-): Promise {
- const { _id, name, config } = req.body;
+ req: MonkeyTypes.Request2
+): Promise {
const { uid } = req.ctx.decodedToken;
- await PresetDAL.editPreset(uid, _id, name, config);
+ await PresetDAL.editPreset(uid, req.body);
- return new MonkeyResponse("Preset updated");
+ return new MonkeyResponse2("Preset updated", null);
}
export async function removePreset(
- req: MonkeyTypes.Request
-): Promise {
+ req: MonkeyTypes.Request2
+): Promise {
const { presetId } = req.params;
const { uid } = req.ctx.decodedToken;
- await PresetDAL.removePreset(uid, presetId as string);
+ await PresetDAL.removePreset(uid, presetId);
- return new MonkeyResponse("Preset deleted");
+ return new MonkeyResponse2("Preset deleted", null);
}
diff --git a/backend/src/api/controllers/psa.ts b/backend/src/api/controllers/psa.ts
index 18a3e31b93d9..721527eef8ea 100644
--- a/backend/src/api/controllers/psa.ts
+++ b/backend/src/api/controllers/psa.ts
@@ -1,7 +1,11 @@
+import { GetPsaResponse } from "@monkeytype/contracts/psas";
import * as PsaDAL from "../../dal/psa";
-import { MonkeyResponse } from "../../utils/monkey-response";
+import { MonkeyResponse2 } from "../../utils/monkey-response";
+import { replaceObjectIds } from "../../utils/misc";
-export async function getPsas(): Promise {
+export async function getPsas(
+ _req: MonkeyTypes.Request2
+): Promise {
const data = await PsaDAL.get();
- return new MonkeyResponse("PSAs retrieved", data);
+ return new MonkeyResponse2("PSAs retrieved", replaceObjectIds(data));
}
diff --git a/backend/src/api/controllers/public.ts b/backend/src/api/controllers/public.ts
index f49dca0310ff..505f5caa9bba 100644
--- a/backend/src/api/controllers/public.ts
+++ b/backend/src/api/controllers/public.ts
@@ -1,21 +1,22 @@
+import {
+ GetSpeedHistogramQuery,
+ GetSpeedHistogramResponse,
+ GetTypingStatsResponse,
+} from "@monkeytype/contracts/public";
import * as PublicDAL from "../../dal/public";
-import { MonkeyResponse } from "../../utils/monkey-response";
+import { MonkeyResponse2 } from "../../utils/monkey-response";
-export async function getPublicSpeedHistogram(
- req: MonkeyTypes.Request
-): Promise {
+export async function getSpeedHistogram(
+ req: MonkeyTypes.Request2
+): Promise {
const { language, mode, mode2 } = req.query;
- const data = await PublicDAL.getSpeedHistogram(
- language as string,
- mode as string,
- mode2 as string
- );
- return new MonkeyResponse("Public speed histogram retrieved", data);
+ const data = await PublicDAL.getSpeedHistogram(language, mode, mode2);
+ return new MonkeyResponse2("Public speed histogram retrieved", data);
}
-export async function getPublicTypingStats(
- _req: MonkeyTypes.Request
-): Promise {
+export async function getTypingStats(
+ _req: MonkeyTypes.Request2
+): Promise {
const data = await PublicDAL.getTypingStats();
- return new MonkeyResponse("Public typing stats retrieved", data);
+ return new MonkeyResponse2("Public typing stats retrieved", data);
}
diff --git a/backend/src/api/controllers/quote.ts b/backend/src/api/controllers/quote.ts
index 53d006432cc7..5f48fcd1f99f 100644
--- a/backend/src/api/controllers/quote.ts
+++ b/backend/src/api/controllers/quote.ts
@@ -6,9 +6,9 @@ import * as NewQuotesDAL from "../../dal/new-quotes";
import * as QuoteRatingsDAL from "../../dal/quote-ratings";
import MonkeyError from "../../utils/error";
import { verify } from "../../utils/captcha";
-import Logger from "../../utils/logger";
import { MonkeyResponse } from "../../utils/monkey-response";
import { ObjectId } from "mongodb";
+import { addLog } from "../../dal/logs";
async function verifyCaptcha(captcha: string): Promise {
if (!(await verify(captcha))) {
@@ -70,7 +70,7 @@ export async function approveQuote(
}
const data = await NewQuotesDAL.approve(quoteId, editText, editSource, name);
- void Logger.logToDb("system_quote_approved", data, uid);
+ void addLog("system_quote_approved", data, uid);
return new MonkeyResponse(data.message, data.quote);
}
diff --git a/backend/src/api/controllers/result.ts b/backend/src/api/controllers/result.ts
index 0f310091046f..f4cc3ec08c71 100644
--- a/backend/src/api/controllers/result.ts
+++ b/backend/src/api/controllers/result.ts
@@ -5,14 +5,14 @@ import {
getStartOfDayTimestamp,
isDevEnvironment,
mapRange,
+ replaceObjectId,
roundTo2,
stdDev,
- stringToNumberOrDefault,
} from "../../utils/misc";
import objectHash from "object-hash";
import Logger from "../../utils/logger";
import "dotenv/config";
-import { MonkeyResponse } from "../../utils/monkey-response";
+import { MonkeyResponse2 } from "../../utils/monkey-response";
import MonkeyError from "../../utils/error";
import { areFunboxesCompatible, isTestTooShort } from "../../utils/validation";
import {
@@ -31,11 +31,30 @@ import AutoRoleList from "../../constants/auto-roles";
import * as UserDAL from "../../dal/user";
import { buildMonkeyMail } from "../../utils/monkey-mail";
import FunboxList from "../../constants/funbox-list";
-import _ from "lodash";
+import _, { omit } from "lodash";
import * as WeeklyXpLeaderboard from "../../services/weekly-xp-leaderboard";
import { UAParser } from "ua-parser-js";
import { canFunboxGetPb } from "../../utils/pb";
-import { buildDbResult } from "../../utils/result";
+import { buildDbResult, replaceLegacyValues } from "../../utils/result";
+import { Configuration } from "@monkeytype/contracts/schemas/configuration";
+import { addLog } from "../../dal/logs";
+import {
+ AddResultRequest,
+ AddResultResponse,
+ GetLastResultResponse,
+ GetResultsQuery,
+ GetResultsResponse,
+ UpdateResultTagsRequest,
+ UpdateResultTagsResponse,
+} from "@monkeytype/contracts/results";
+import {
+ CompletedEvent,
+ KeyStats,
+ Result,
+ PostResultResponse,
+ XpBreakdown,
+} from "@monkeytype/contracts/schemas/results";
+import { Mode } from "@monkeytype/contracts/schemas/shared";
try {
if (!anticheatImplemented()) throw new Error("undefined");
@@ -54,10 +73,11 @@ try {
}
export async function getResults(
- req: MonkeyTypes.Request
-): Promise {
+ req: MonkeyTypes.Request2
+): Promise {
const { uid } = req.ctx.decodedToken;
const premiumFeaturesEnabled = req.ctx.configuration.users.premium.enabled;
+ const { onOrAfterTimestamp = NaN, offset = 0 } = req.query;
const userHasPremium = await UserDAL.checkIfUserIsPremium(uid);
const maxLimit =
@@ -65,15 +85,9 @@ export async function getResults(
? req.ctx.configuration.results.limits.premiumUser
: req.ctx.configuration.results.limits.regularUser;
- const onOrAfterTimestamp = parseInt(
- req.query["onOrAfterTimestamp"] as string,
- 10
- );
- let limit = stringToNumberOrDefault(
- req.query["limit"] as string,
- Math.min(req.ctx.configuration.results.maxBatchSize, maxLimit)
- );
- const offset = stringToNumberOrDefault(req.query["offset"] as string, 0);
+ let limit =
+ req.query.limit ??
+ Math.min(req.ctx.configuration.results.maxBatchSize, maxLimit);
//check if premium features are disabled and current call exceeds the limit for regular users
if (
@@ -98,7 +112,7 @@ export async function getResults(
limit,
offset,
});
- void Logger.logToDb(
+ void addLog(
"user_results_requested",
{
limit,
@@ -108,30 +122,30 @@ export async function getResults(
},
uid
);
- return new MonkeyResponse("Results retrieved", results);
+ return new MonkeyResponse2("Results retrieved", results.map(convertResult));
}
export async function getLastResult(
- req: MonkeyTypes.Request
-): Promise {
+ req: MonkeyTypes.Request2
+): Promise {
const { uid } = req.ctx.decodedToken;
const results = await ResultDAL.getLastResult(uid);
- return new MonkeyResponse("Result retrieved", results);
+ return new MonkeyResponse2("Result retrieved", convertResult(results));
}
export async function deleteAll(
- req: MonkeyTypes.Request
-): Promise {
+ req: MonkeyTypes.Request2
+): Promise {
const { uid } = req.ctx.decodedToken;
await ResultDAL.deleteAll(uid);
- void Logger.logToDb("user_results_deleted", "", uid);
- return new MonkeyResponse("All results deleted");
+ void addLog("user_results_deleted", "", uid);
+ return new MonkeyResponse2("All results deleted", null);
}
export async function updateTags(
- req: MonkeyTypes.Request
-): Promise {
+ req: MonkeyTypes.Request2
+): Promise {
const { uid } = req.ctx.decodedToken;
const { tagIds, resultId } = req.body;
@@ -159,14 +173,14 @@ export async function updateTags(
const user = await UserDAL.getPartialUser(uid, "update tags", ["tags"]);
const tagPbs = await UserDAL.checkIfTagPb(uid, user, result);
- return new MonkeyResponse("Result tags updated", {
+ return new MonkeyResponse2("Result tags updated", {
tagPbs,
});
}
export async function addResult(
- req: MonkeyTypes.Request
-): Promise {
+ req: MonkeyTypes.Request2
+): Promise {
const { uid } = req.ctx.decodedToken;
const user = await UserDAL.getUser(uid, "add result");
@@ -178,32 +192,20 @@ export async function addResult(
);
}
- const completedEvent = Object.assign(
- {},
- req.body.result
- ) as SharedTypes.CompletedEvent;
- if (!user.lbOptOut && completedEvent.acc < 75) {
- throw new MonkeyError(
- 400,
- "Cannot submit a result with less than 75% accuracy"
- );
- }
+ const completedEvent = req.body.result;
completedEvent.uid = uid;
+
if (isTestTooShort(completedEvent)) {
const status = MonkeyStatusCodes.TEST_TOO_SHORT;
throw new MonkeyError(status.code, status.message);
}
const resulthash = completedEvent.hash;
- if (resulthash === undefined || resulthash === "") {
- throw new MonkeyError(400, "Missing result hash");
- }
- delete completedEvent.hash;
- delete completedEvent.stringified;
if (req.ctx.configuration.results.objectHashCheckEnabled) {
- const serverhash = objectHash(completedEvent);
+ const objectToHash = omit(completedEvent, "hash");
+ const serverhash = objectHash(objectToHash);
if (serverhash !== resulthash) {
- void Logger.logToDb(
+ void addLog(
"incorrect_result_hash",
{
serverhash,
@@ -215,6 +217,8 @@ export async function addResult(
const status = MonkeyStatusCodes.RESULT_HASH_INVALID;
throw new MonkeyError(status.code, "Incorrect result hash");
}
+ } else {
+ Logger.warning("Object hash check is disabled, skipping hash check");
}
if (completedEvent.funbox) {
@@ -228,11 +232,12 @@ export async function addResult(
throw new MonkeyError(400, "Impossible funbox combination");
}
+ let keySpacingStats: KeyStats | undefined = undefined;
if (
completedEvent.keySpacing !== "toolong" &&
completedEvent.keySpacing.length > 0
) {
- completedEvent.keySpacingStats = {
+ keySpacingStats = {
average:
completedEvent.keySpacing.reduce(
(previous, current) => (current += previous)
@@ -241,11 +246,12 @@ export async function addResult(
};
}
+ let keyDurationStats: KeyStats | undefined = undefined;
if (
completedEvent.keyDuration !== "toolong" &&
completedEvent.keyDuration.length > 0
) {
- completedEvent.keyDurationStats = {
+ keyDurationStats = {
average:
completedEvent.keyDuration.reduce(
(previous, current) => (current += previous)
@@ -258,9 +264,9 @@ export async function addResult(
if (
!validateResult(
completedEvent,
- ((req.headers["x-client-version"] as string) ||
- req.headers["client-version"]) as string,
- JSON.stringify(new UAParser(req.headers["user-agent"]).getResult()),
+ ((req.raw.headers["x-client-version"] as string) ||
+ req.raw.headers["client-version"]) as string,
+ JSON.stringify(new UAParser(req.raw.headers["user-agent"]).getResult()),
user.lbOptOut === true
)
) {
@@ -304,7 +310,7 @@ export async function addResult(
const earliestPossible = (lastResultTimestamp ?? 0) + testDurationMilis;
const nowNoMilis = Math.floor(Date.now() / 1000) * 1000;
if (lastResultTimestamp && nowNoMilis < earliestPossible - 1000) {
- void Logger.logToDb(
+ void addLog(
"invalid_result_spacing",
{
lastTimestamp: lastResultTimestamp,
@@ -325,10 +331,9 @@ export async function addResult(
completedEvent.wpm > 130 &&
completedEvent.testDuration < 122 &&
(user.verified === false || user.verified === undefined) &&
- user.lbOptOut !== true &&
- user.banned !== true //no need to check again if user is already banned
+ user.lbOptOut !== true
) {
- if (!completedEvent.keySpacingStats || !completedEvent.keyDurationStats) {
+ if (!keySpacingStats || !keyDurationStats) {
const status = MonkeyStatusCodes.MISSING_KEY_DATA;
throw new MonkeyError(status.code, "Missing key data");
}
@@ -336,7 +341,9 @@ export async function addResult(
throw new MonkeyError(400, "Old key data format");
}
if (anticheatImplemented()) {
- if (!validateKeys(completedEvent, uid)) {
+ if (
+ !validateKeys(completedEvent, keySpacingStats, keyDurationStats, uid)
+ ) {
//autoban
const autoBanConfig = req.ctx.configuration.users.autoBan;
if (autoBanConfig.enabled) {
@@ -374,7 +381,7 @@ export async function addResult(
if (req.ctx.configuration.users.lastHashesCheck.enabled) {
let lastHashes = user.lastReultHashes ?? [];
if (lastHashes.includes(resulthash)) {
- void Logger.logToDb(
+ void addLog(
"duplicate_result",
{
lastHashes,
@@ -395,21 +402,13 @@ export async function addResult(
}
}
- if (completedEvent.keyDurationStats) {
- completedEvent.keyDurationStats.average = roundTo2(
- completedEvent.keyDurationStats.average
- );
- completedEvent.keyDurationStats.sd = roundTo2(
- completedEvent.keyDurationStats.sd
- );
+ if (keyDurationStats) {
+ keyDurationStats.average = roundTo2(keyDurationStats.average);
+ keyDurationStats.sd = roundTo2(keyDurationStats.sd);
}
- if (completedEvent.keySpacingStats) {
- completedEvent.keySpacingStats.average = roundTo2(
- completedEvent.keySpacingStats.average
- );
- completedEvent.keySpacingStats.sd = roundTo2(
- completedEvent.keySpacingStats.sd
- );
+ if (keySpacingStats) {
+ keySpacingStats.average = roundTo2(keySpacingStats.average);
+ keySpacingStats.sd = roundTo2(keySpacingStats.sd);
}
let isPb = false;
@@ -470,7 +469,7 @@ export async function addResult(
user.banned !== true &&
user.lbOptOut !== true &&
(isDevEnvironment() || (user.timeTyping ?? 0) > 7200) &&
- completedEvent.stopOnLetter !== true;
+ !completedEvent.stopOnLetter;
const selectedBadgeId = user.inventory?.badges?.find((b) => b.selected)?.id;
const isPremium =
@@ -583,13 +582,20 @@ export async function addResult(
}
const dbresult = buildDbResult(completedEvent, user.name, isPb);
+ if (keySpacingStats !== undefined) {
+ dbresult.keySpacingStats = keySpacingStats;
+ }
+ if (keyDurationStats !== undefined) {
+ dbresult.keyDurationStats = keyDurationStats;
+ }
+
const addedResult = await ResultDAL.addResult(uid, dbresult);
await UserDAL.incrementXp(uid, xpGained.xp);
await UserDAL.incrementTestActivity(user, completedEvent.timestamp);
if (isPb) {
- void Logger.logToDb(
+ void addLog(
"user_new_pb",
`${completedEvent.mode + " " + completedEvent.mode2} ${
completedEvent.wpm
@@ -600,12 +606,10 @@ export async function addResult(
);
}
- const data: Omit & {
- insertedId: ObjectId;
- } = {
+ const data: PostResultResponse = {
isPb,
tagPbs,
- insertedId: addedResult.insertedId,
+ insertedId: addedResult.insertedId.toHexString(),
xp: xpGained.xp,
dailyXpBonus: xpGained.dailyBonus ?? false,
xpBreakdown: xpGained.breakdown ?? {},
@@ -620,20 +624,20 @@ export async function addResult(
data.weeklyXpLeaderboardRank = weeklyXpLeaderboardRank;
}
- incrementResult(completedEvent);
+ incrementResult(completedEvent, dbresult.isPb);
- return new MonkeyResponse("Result saved", data);
+ return new MonkeyResponse2("Result saved", data);
}
type XpResult = {
xp: number;
dailyBonus?: boolean;
- breakdown?: Record;
+ breakdown?: XpBreakdown;
};
async function calculateXp(
- result: SharedTypes.CompletedEvent,
- xpConfiguration: SharedTypes.Configuration["users"]["xp"],
+ result: CompletedEvent,
+ xpConfiguration: Configuration["users"]["xp"],
uid: string,
currentTotalXp: number,
streak: number
@@ -665,10 +669,10 @@ async function calculateXp(
};
}
- const breakdown: Record = {};
+ const breakdown: XpBreakdown = {};
const baseXp = Math.round((testDuration - afkDuration) * 2);
- breakdown["base"] = baseXp;
+ breakdown.base = baseXp;
let modifier = 1;
@@ -678,7 +682,7 @@ async function calculateXp(
if (acc === 100) {
modifier += 0.5;
- breakdown["100%"] = Math.round(baseXp * 0.5);
+ breakdown.fullAccuracy = Math.round(baseXp * 0.5);
} else if (correctedEverything) {
// corrected everything bonus
modifier += 0.25;
@@ -688,16 +692,16 @@ async function calculateXp(
if (mode === "quote") {
// real sentences bonus
modifier += 0.5;
- breakdown["quote"] = Math.round(baseXp * 0.5);
+ breakdown.quote = Math.round(baseXp * 0.5);
} else {
// punctuation bonus
if (punctuation) {
modifier += 0.4;
- breakdown["punctuation"] = Math.round(baseXp * 0.4);
+ breakdown.punctuation = Math.round(baseXp * 0.4);
}
if (numbers) {
modifier += 0.1;
- breakdown["numbers"] = Math.round(baseXp * 0.1);
+ breakdown.numbers = Math.round(baseXp * 0.1);
}
}
@@ -709,7 +713,7 @@ async function calculateXp(
});
if (funboxModifier > 0) {
modifier += funboxModifier;
- breakdown["funbox"] = Math.round(baseXp * funboxModifier);
+ breakdown.funbox = Math.round(baseXp * funboxModifier);
}
}
@@ -727,7 +731,7 @@ async function calculateXp(
if (streakModifier > 0) {
modifier += streakModifier;
- breakdown["streak"] = Math.round(baseXp * streakModifier);
+ breakdown.streak = Math.round(baseXp * streakModifier);
}
}
@@ -738,10 +742,10 @@ async function calculateXp(
if (modifier < 0) modifier = 0;
incompleteXp += Math.round(it.seconds * modifier);
});
- breakdown["incomplete"] = incompleteXp;
+ breakdown.incomplete = incompleteXp;
} else if (incompleteTestSeconds && incompleteTestSeconds > 0) {
incompleteXp = Math.round(incompleteTestSeconds);
- breakdown["incomplete"] = incompleteXp;
+ breakdown.incomplete = incompleteXp;
}
const accuracyModifier = (acc - 50) / 50;
@@ -765,14 +769,14 @@ async function calculateXp(
Math.min(maxDailyBonus, proportionalXp),
minDailyBonus
);
- breakdown["daily"] = dailyBonus;
+ breakdown.daily = dailyBonus;
}
}
const xpWithModifiers = Math.round(baseXp * modifier);
const xpAfterAccuracy = Math.round(xpWithModifiers * accuracyModifier);
- breakdown["accPenalty"] = xpWithModifiers - xpAfterAccuracy;
+ breakdown.accPenalty = xpWithModifiers - xpAfterAccuracy;
const totalXp =
Math.round((xpAfterAccuracy + incompleteXp) * gainMultiplier) + dailyBonus;
@@ -782,7 +786,7 @@ async function calculateXp(
// "configMultiplier",
// Math.round((xpAfterAccuracy + incompleteXp) * (gainMultiplier - 1)),
// ]);
- breakdown["configMultiplier"] = gainMultiplier;
+ breakdown.configMultiplier = gainMultiplier;
}
const isAwardingDailyBonus = dailyBonus > 0;
@@ -793,3 +797,7 @@ async function calculateXp(
breakdown,
};
}
+
+function convertResult(db: MonkeyTypes.DBResult): Result {
+ return replaceObjectId(replaceLegacyValues(db));
+}
diff --git a/backend/src/api/controllers/user.ts b/backend/src/api/controllers/user.ts
index 5ff96c6860f5..0bca837afe17 100644
--- a/backend/src/api/controllers/user.ts
+++ b/backend/src/api/controllers/user.ts
@@ -1,7 +1,6 @@
import _ from "lodash";
import * as UserDAL from "../../dal/user";
import MonkeyError from "../../utils/error";
-import Logger from "../../utils/logger";
import { MonkeyResponse } from "../../utils/monkey-response";
import * as DiscordUtils from "../../utils/discord";
import {
@@ -11,7 +10,7 @@ import {
sanitizeString,
} from "../../utils/misc";
import GeorgeQueue from "../../queues/george-queue";
-import admin, { FirebaseError } from "firebase-admin";
+import admin, { type FirebaseError } from "firebase-admin";
import { deleteAllApeKeys } from "../../dal/ape-keys";
import { deleteAllPresets } from "../../dal/preset";
import { deleteAll as deleteAllResults } from "../../dal/result";
@@ -25,14 +24,34 @@ import * as ReportDAL from "../../dal/report";
import emailQueue from "../../queues/email-queue";
import FirebaseAdmin from "../../init/firebase-admin";
import * as AuthUtil from "../../utils/auth";
-
import * as Dates from "date-fns";
import { UTCDateMini } from "@date-fns/utc";
import * as BlocklistDal from "../../dal/blocklist";
+import { Mode, Mode2 } from "@monkeytype/contracts/schemas/shared";
+import {
+ AllTimeLbs,
+ CountByYearAndDay,
+ RankAndCount,
+ TestActivity,
+ UserProfile,
+ UserProfileDetails,
+} from "@monkeytype/shared-types";
+import { addImportantLog, addLog, deleteUserLogs } from "../../dal/logs";
+import { sendForgotPasswordEmail as authSendForgotPasswordEmail } from "../../utils/auth";
async function verifyCaptcha(captcha: string): Promise {
- if (!(await verify(captcha))) {
- throw new MonkeyError(422, "Captcha check failed");
+ let verified = false;
+ try {
+ verified = await verify(captcha);
+ } catch (e) {
+ //fetch to recaptcha api can sometimes fail
+ throw new MonkeyError(
+ 422,
+ "Request to the Captcha API failed, please try again later"
+ );
+ }
+ if (!verified) {
+ throw new MonkeyError(422, "Captcha challenge failed");
}
}
@@ -60,7 +79,7 @@ export async function createNewUser(
}
await UserDAL.addUser(name, email, uid);
- void Logger.logToDb("user_created", `${name} ${email}`, uid);
+ void addImportantLog("user_created", `${name} ${email}`, uid);
return new MonkeyResponse("User created");
} catch (e) {
@@ -78,11 +97,15 @@ export async function sendVerificationEmail(
await admin
.auth()
.getUser(uid)
- .catch((e) => {
+ .catch((e: unknown) => {
throw new MonkeyError(
500, // this should never happen, but it does. it mightve been caused by auth token cache, will see if disabling cache fixes it
"Auth user not found, even though the token got decoded",
- JSON.stringify({ uid, email, stack: e.stack }),
+ JSON.stringify({
+ uid,
+ email,
+ stack: e instanceof Error ? e.stack : JSON.stringify(e),
+ }),
uid
);
})
@@ -151,29 +174,7 @@ export async function sendForgotPasswordEmail(
req: MonkeyTypes.Request
): Promise {
const { email } = req.body;
-
- try {
- const uid = (await FirebaseAdmin().auth().getUserByEmail(email)).uid;
- const userInfo = await UserDAL.getPartialUser(
- uid,
- "request forgot password email",
- ["name"]
- );
-
- const link = await FirebaseAdmin()
- .auth()
- .generatePasswordResetLink(email, {
- url: isDevEnvironment()
- ? "http://localhost:3000"
- : "https://monkeytype.com",
- });
-
- await emailQueue.sendForgotPasswordEmail(email, userInfo.name, link);
- } catch {
- return new MonkeyResponse(
- "Password reset request received. If the email is valid, you will receive an email shortly."
- );
- }
+ await authSendForgotPasswordEmail(email);
return new MonkeyResponse(
"Password reset request received. If the email is valid, you will receive an email shortly."
);
@@ -198,6 +199,7 @@ export async function deleteUser(
//cleanup database
await Promise.all([
UserDAL.deleteUser(uid),
+ deleteUserLogs(uid),
deleteAllApeKeys(uid),
deleteAllPresets(uid),
deleteConfig(uid),
@@ -211,7 +213,7 @@ export async function deleteUser(
//delete user from
await AuthUtil.deleteUser(uid);
- void Logger.logToDb(
+ void addImportantLog(
"user_deleted",
`${userInfo.email} ${userInfo.name}`,
uid
@@ -251,7 +253,7 @@ export async function resetUser(
promises.push(GeorgeQueue.unlinkDiscord(userInfo.discordId, uid));
}
await Promise.all(promises);
- void Logger.logToDb("user_reset", `${userInfo.email} ${userInfo.name}`, uid);
+ void addImportantLog("user_reset", `${userInfo.email} ${userInfo.name}`, uid);
return new MonkeyResponse("User reset");
}
@@ -281,7 +283,7 @@ export async function updateName(
}
await UserDAL.updateName(uid, name, user.name);
- void Logger.logToDb(
+ void addImportantLog(
"user_name_updated",
`changed name from ${user.name} to ${name}`,
uid
@@ -300,7 +302,7 @@ export async function clearPb(
uid,
req.ctx.configuration.dailyLeaderboards
);
- void Logger.logToDb("user_cleared_pbs", "", uid);
+ void addImportantLog("user_cleared_pbs", "", uid);
return new MonkeyResponse("User's PB cleared");
}
@@ -315,7 +317,7 @@ export async function optOutOfLeaderboards(
uid,
req.ctx.configuration.dailyLeaderboards
);
- void Logger.logToDb("user_opted_out_of_leaderboards", "", uid);
+ void addImportantLog("user_opted_out_of_leaderboards", "", uid);
return new MonkeyResponse("User opted out of leaderboards");
}
@@ -369,7 +371,7 @@ export async function updateEmail(
}
}
- void Logger.logToDb(
+ void addImportantLog(
"user_email_updated",
`changed email to ${newEmail}`,
uid
@@ -453,7 +455,7 @@ export async function getUser(
};
const agentLog = buildAgentLog(req);
- void Logger.logToDb("user_data_requested", agentLog, uid);
+ void addLog("user_data_requested", agentLog, uid);
void UserDAL.logIpAddress(uid, agentLog.ip, userInfo);
let inboxUnreadSize = 0;
@@ -548,7 +550,7 @@ export async function linkDiscord(
await UserDAL.linkDiscord(uid, discordId, discordAvatar);
await GeorgeQueue.linkDiscord(discordId, uid);
- void Logger.logToDb("user_discord_link", `linked to ${discordId}`, uid);
+ void addImportantLog("user_discord_link", `linked to ${discordId}`, uid);
return new MonkeyResponse("Discord account linked", {
discordId,
@@ -577,7 +579,7 @@ export async function unlinkDiscord(
await GeorgeQueue.unlinkDiscord(discordId, uid);
await UserDAL.unlinkDiscord(uid);
- void Logger.logToDb("user_discord_unlinked", discordId, uid);
+ void addImportantLog("user_discord_unlinked", discordId, uid);
return new MonkeyResponse("Discord account unlinked");
}
@@ -661,8 +663,7 @@ export async function updateLbMemory(
): Promise {
const { uid } = req.ctx.decodedToken;
const { mode, language, rank } = req.body;
- const mode2 = req.body
- .mode2 as SharedTypes.Config.Mode2;
+ const mode2 = req.body.mode2 as Mode2;
await UserDAL.updateLbMemory(uid, mode, mode2, language, rank);
return new MonkeyResponse("Leaderboard memory updated");
@@ -837,7 +838,7 @@ export async function getProfile(
details: profileDetails,
allTimeLbs,
uid: user.uid,
- } as SharedTypes.UserProfile;
+ } as UserProfile;
return new MonkeyResponse("Profile retrieved", profileData);
}
@@ -865,13 +866,13 @@ export async function updateProfile(
}
});
- const profileDetailsUpdates: Partial = {
+ const profileDetailsUpdates: Partial = {
bio: sanitizeString(bio),
keyboard: sanitizeString(keyboard),
socialProfiles: _.mapValues(
socialProfiles,
sanitizeString
- ) as SharedTypes.UserProfileDetails["socialProfiles"],
+ ) as UserProfileDetails["socialProfiles"],
};
await UserDAL.updateProfile(uid, profileDetailsUpdates, user.inventory);
@@ -950,6 +951,8 @@ export async function setStreakHourOffset(
await UserDAL.setStreakHourOffset(uid, hourOffset);
+ void addImportantLog("user_streak_hour_offset_set", { hourOffset }, uid);
+
return new MonkeyResponse("Streak hour offset set");
}
@@ -973,6 +976,8 @@ export async function toggleBan(
if (discordIdIsValid) await GeorgeQueue.userBanned(discordId, true);
}
+ void addImportantLog("user_ban_toggled", { banned: !user.banned }, uid);
+
return new MonkeyResponse(`Ban toggled`, {
banned: !user.banned,
});
@@ -983,10 +988,11 @@ export async function revokeAllTokens(
): Promise {
const { uid } = req.ctx.decodedToken;
await AuthUtil.revokeTokensByUid(uid);
+ void addImportantLog("user_tokens_revoked", "", uid);
return new MonkeyResponse("All tokens revoked");
}
-async function getAllTimeLbs(uid: string): Promise {
+async function getAllTimeLbs(uid: string): Promise {
const allTime15English = await LeaderboardsDAL.getRank(
"time",
"15",
@@ -1007,7 +1013,7 @@ async function getAllTimeLbs(uid: string): Promise {
: ({
rank: allTime15English.rank,
count: allTime15English.count,
- } as SharedTypes.RankAndCount);
+ } as RankAndCount);
const english60 =
allTime60English === false
@@ -1015,7 +1021,7 @@ async function getAllTimeLbs(uid: string): Promise {
: ({
rank: allTime60English.rank,
count: allTime60English.count,
- } as SharedTypes.RankAndCount);
+ } as RankAndCount);
return {
time: {
@@ -1030,8 +1036,8 @@ async function getAllTimeLbs(uid: string): Promise {
}
export function generateCurrentTestActivity(
- testActivity: SharedTypes.CountByYearAndDay | undefined
-): SharedTypes.TestActivity | undefined {
+ testActivity: CountByYearAndDay | undefined
+): TestActivity | undefined {
const thisYear = Dates.startOfYear(new UTCDateMini());
const lastYear = Dates.startOfYear(Dates.subYears(thisYear, 1));
diff --git a/backend/src/api/routes/admin.ts b/backend/src/api/routes/admin.ts
index 4c1bdb93f98f..8822495679f8 100644
--- a/backend/src/api/routes/admin.ts
+++ b/backend/src/api/routes/admin.ts
@@ -1,107 +1,46 @@
// import joi from "joi";
-import { Router } from "express";
-import { authenticateRequest } from "../../middlewares/auth";
-import * as AdminController from "../controllers/admin";
import { adminLimit } from "../../middlewares/rate-limit";
-import { sendForgotPasswordEmail, toggleBan } from "../controllers/user";
-import joi from "joi";
+import * as AdminController from "../controllers/admin";
+
+import { adminContract } from "@monkeytype/contracts/admin";
+import { initServer } from "@ts-rest/express";
import { validate } from "../../middlewares/configuration";
import { checkIfUserIsAdmin } from "../../middlewares/permission";
-import { asyncHandler } from "../../middlewares/utility";
-import { validateRequest } from "../../middlewares/validation";
+import { callController } from "../ts-rest-adapter";
-const router = Router();
+const commonMiddleware = [
+ adminLimit,
-router.use(
validate({
criteria: (configuration) => {
return configuration.admin.endpointsEnabled;
},
invalidMessage: "Admin endpoints are currently disabled.",
- })
-);
-
-router.get(
- "/",
- adminLimit,
- authenticateRequest({
- noCache: true,
- }),
- checkIfUserIsAdmin(),
- asyncHandler(AdminController.test)
-);
-
-router.post(
- "/toggleBan",
- adminLimit,
- authenticateRequest({
- noCache: true,
- }),
- checkIfUserIsAdmin(),
- validateRequest({
- body: {
- uid: joi.string().required().token(),
- },
- }),
- asyncHandler(toggleBan)
-);
-
-router.post(
- "/report/accept",
- authenticateRequest({
- noCache: true,
}),
checkIfUserIsAdmin(),
- validateRequest({
- body: {
- reports: joi
- .array()
- .items(
- joi.object({
- reportId: joi.string().required(),
- })
- )
- .required(),
- },
- }),
- asyncHandler(AdminController.acceptReports)
-);
-
-router.post(
- "/report/reject",
- authenticateRequest({
- noCache: true,
- }),
- checkIfUserIsAdmin(),
- validateRequest({
- body: {
- reports: joi
- .array()
- .items(
- joi.object({
- reportId: joi.string().required(),
- reason: joi.string().optional(),
- })
- )
- .required(),
- },
- }),
- asyncHandler(AdminController.rejectReports)
-);
-
-router.post(
- "/sendForgotPasswordEmail",
- adminLimit,
- authenticateRequest({
- noCache: true,
- }),
- checkIfUserIsAdmin(),
- validateRequest({
- body: {
- email: joi.string().email().required(),
- },
- }),
- asyncHandler(sendForgotPasswordEmail)
-);
-
-export default router;
+];
+
+const s = initServer();
+export default s.router(adminContract, {
+ test: {
+ middleware: commonMiddleware,
+ handler: async (r) => callController(AdminController.test)(r),
+ },
+ toggleBan: {
+ middleware: commonMiddleware,
+ handler: async (r) => callController(AdminController.toggleBan)(r),
+ },
+ acceptReports: {
+ middleware: commonMiddleware,
+ handler: async (r) => callController(AdminController.acceptReports)(r),
+ },
+ rejectReports: {
+ middleware: commonMiddleware,
+ handler: async (r) => callController(AdminController.rejectReports)(r),
+ },
+ sendForgotPasswordEmail: {
+ middleware: commonMiddleware,
+ handler: async (r) =>
+ callController(AdminController.sendForgotPasswordEmail)(r),
+ },
+});
diff --git a/backend/src/api/routes/ape-keys.ts b/backend/src/api/routes/ape-keys.ts
index 1e49bc892c46..56cb0a46519e 100644
--- a/backend/src/api/routes/ape-keys.ts
+++ b/backend/src/api/routes/ape-keys.ts
@@ -1,91 +1,42 @@
-import joi from "joi";
-import { Router } from "express";
-import { authenticateRequest } from "../../middlewares/auth";
-import * as ApeKeyController from "../controllers/ape-key";
+import { apeKeysContract } from "@monkeytype/contracts/ape-keys";
+import { initServer } from "@ts-rest/express";
import * as RateLimit from "../../middlewares/rate-limit";
+import * as ApeKeyController from "../controllers/ape-key";
+import { callController } from "../ts-rest-adapter";
import { checkUserPermissions } from "../../middlewares/permission";
import { validate } from "../../middlewares/configuration";
-import { asyncHandler } from "../../middlewares/utility";
-import { validateRequest } from "../../middlewares/validation";
-
-const apeKeyNameSchema = joi
- .string()
- .regex(/^[0-9a-zA-Z_.-]+$/)
- .max(20)
- .messages({
- "string.pattern.base": "Invalid ApeKey name",
- "string.max": "ApeKey name exceeds maximum of 20 characters",
- });
-
-const checkIfUserCanManageApeKeys = checkUserPermissions({
- criteria: (user) => {
- // Must be an exact check
- return user.canManageApeKeys !== false;
- },
- invalidMessage: "You have lost access to ape keys, please contact support",
-});
-
-const router = Router();
-router.use(
+const commonMiddleware = [
validate({
criteria: (configuration) => {
return configuration.apeKeys.endpointsEnabled;
},
invalidMessage: "ApeKeys are currently disabled.",
- })
-);
-
-router.get(
- "/",
- authenticateRequest(),
- RateLimit.apeKeysGet,
- checkIfUserCanManageApeKeys,
- asyncHandler(ApeKeyController.getApeKeys)
-);
-
-router.post(
- "/",
- authenticateRequest(),
- RateLimit.apeKeysGenerate,
- checkIfUserCanManageApeKeys,
- validateRequest({
- body: {
- name: apeKeyNameSchema.required(),
- enabled: joi.boolean().required(),
- },
}),
- asyncHandler(ApeKeyController.generateApeKey)
-);
-
-router.patch(
- "/:apeKeyId",
- authenticateRequest(),
- RateLimit.apeKeysUpdate,
- checkIfUserCanManageApeKeys,
- validateRequest({
- params: {
- apeKeyId: joi.string().token().required(),
- },
- body: {
- name: apeKeyNameSchema,
- enabled: joi.boolean(),
- },
- }),
- asyncHandler(ApeKeyController.editApeKey)
-);
-
-router.delete(
- "/:apeKeyId",
- authenticateRequest(),
- RateLimit.apeKeysDelete,
- checkIfUserCanManageApeKeys,
- validateRequest({
- params: {
- apeKeyId: joi.string().token().required(),
+ checkUserPermissions(["canManageApeKeys"], {
+ criteria: (user) => {
+ return user.canManageApeKeys ?? true;
},
+ invalidMessage: "You have lost access to ape keys, please contact support",
}),
- asyncHandler(ApeKeyController.deleteApeKey)
-);
+];
-export default router;
+const s = initServer();
+export default s.router(apeKeysContract, {
+ get: {
+ middleware: [...commonMiddleware, RateLimit.apeKeysGet],
+ handler: async (r) => callController(ApeKeyController.getApeKeys)(r),
+ },
+ add: {
+ middleware: [...commonMiddleware, RateLimit.apeKeysGenerate],
+ handler: async (r) => callController(ApeKeyController.generateApeKey)(r),
+ },
+ save: {
+ middleware: [...commonMiddleware, RateLimit.apeKeysUpdate],
+ handler: async (r) => callController(ApeKeyController.editApeKey)(r),
+ },
+ delete: {
+ middleware: [...commonMiddleware, RateLimit.apeKeysDelete],
+ handler: async (r) => callController(ApeKeyController.deleteApeKey)(r),
+ },
+});
diff --git a/backend/src/api/routes/configs.ts b/backend/src/api/routes/configs.ts
index c2e580b628ca..132e74f348de 100644
--- a/backend/src/api/routes/configs.ts
+++ b/backend/src/api/routes/configs.ts
@@ -1,30 +1,23 @@
-import { Router } from "express";
-import { authenticateRequest } from "../../middlewares/auth";
-import configSchema from "../schemas/config-schema";
-import * as ConfigController from "../controllers/config";
+import { configsContract } from "@monkeytype/contracts/configs";
+import { initServer } from "@ts-rest/express";
import * as RateLimit from "../../middlewares/rate-limit";
-import { asyncHandler } from "../../middlewares/utility";
-import { validateRequest } from "../../middlewares/validation";
-
-const router = Router();
+import * as ConfigController from "../controllers/config";
+import { callController } from "../ts-rest-adapter";
-router.get(
- "/",
- authenticateRequest(),
- RateLimit.configGet,
- asyncHandler(ConfigController.getConfig)
-);
+const s = initServer();
-router.patch(
- "/",
- authenticateRequest(),
- RateLimit.configUpdate,
- validateRequest({
- body: {
- config: configSchema.required(),
- },
- }),
- asyncHandler(ConfigController.saveConfig)
-);
+export default s.router(configsContract, {
+ get: {
+ middleware: [RateLimit.configGet],
+ handler: async (r) => callController(ConfigController.getConfig)(r),
+ },
-export default router;
+ save: {
+ middleware: [RateLimit.configUpdate],
+ handler: async (r) => callController(ConfigController.saveConfig)(r),
+ },
+ delete: {
+ middleware: [RateLimit.configDelete],
+ handler: async (r) => callController(ConfigController.deleteConfig)(r),
+ },
+});
diff --git a/backend/src/api/routes/configuration.ts b/backend/src/api/routes/configuration.ts
index 54cc3a963040..f7b7b160d29f 100644
--- a/backend/src/api/routes/configuration.ts
+++ b/backend/src/api/routes/configuration.ts
@@ -1,43 +1,25 @@
-import joi from "joi";
-import { Router } from "express";
-import * as ConfigurationController from "../controllers/configuration";
-import { authenticateRequest } from "../../middlewares/auth";
-import { adminLimit } from "../../middlewares/rate-limit";
-import { asyncHandler, useInProduction } from "../../middlewares/utility";
+import { configurationContract } from "@monkeytype/contracts/configuration";
+import { initServer } from "@ts-rest/express";
import { checkIfUserIsAdmin } from "../../middlewares/permission";
-import { validateRequest } from "../../middlewares/validation";
-
-const router = Router();
-
-router.get("/", asyncHandler(ConfigurationController.getConfiguration));
+import * as RateLimit from "../../middlewares/rate-limit";
+import * as ConfigurationController from "../controllers/configuration";
+import { callController } from "../ts-rest-adapter";
-router.patch(
- "/",
- adminLimit,
- useInProduction([
- authenticateRequest({
- noCache: true,
- }),
- checkIfUserIsAdmin(),
- ]),
- validateRequest({
- body: {
- configuration: joi.object(),
- },
- }),
- asyncHandler(ConfigurationController.updateConfiguration)
-);
+const s = initServer();
-router.get(
- "/schema",
- adminLimit,
- useInProduction([
- authenticateRequest({
- noCache: true,
- }),
- checkIfUserIsAdmin(),
- ]),
- asyncHandler(ConfigurationController.getSchema)
-);
+export default s.router(configurationContract, {
+ get: {
+ handler: async (r) =>
+ callController(ConfigurationController.getConfiguration)(r),
+ },
-export default router;
+ update: {
+ middleware: [checkIfUserIsAdmin(), RateLimit.adminLimit],
+ handler: async (r) =>
+ callController(ConfigurationController.updateConfiguration)(r),
+ },
+ getSchema: {
+ middleware: [checkIfUserIsAdmin(), RateLimit.adminLimit],
+ handler: async (r) => callController(ConfigurationController.getSchema)(r),
+ },
+});
diff --git a/backend/src/api/routes/dev.ts b/backend/src/api/routes/dev.ts
index 2c2dd2a42cb4..3b4678a20bc6 100644
--- a/backend/src/api/routes/dev.ts
+++ b/backend/src/api/routes/dev.ts
@@ -1,35 +1,15 @@
-import { Router } from "express";
-import joi from "joi";
-import { createTestData } from "../controllers/dev";
-import { isDevEnvironment } from "../../utils/misc";
-import { validate } from "../../middlewares/configuration";
-import { validateRequest } from "../../middlewares/validation";
-import { asyncHandler } from "../../middlewares/utility";
+import { devContract } from "@monkeytype/contracts/dev";
+import { initServer } from "@ts-rest/express";
-const router = Router();
+import * as DevController from "../controllers/dev";
+import { callController } from "../ts-rest-adapter";
+import { onlyAvailableOnDev } from "../../middlewares/utility";
-router.use(
- validate({
- criteria: () => {
- return isDevEnvironment();
- },
- invalidMessage: "Development endpoints are only available in DEV mode.",
- })
-);
+const s = initServer();
-router.post(
- "/generateData",
- validateRequest({
- body: {
- username: joi.string().required(),
- createUser: joi.boolean().optional(),
- firstTestTimestamp: joi.number().optional(),
- lastTestTimestamp: joi.number().optional(),
- minTestsPerDay: joi.number().optional(),
- maxTestsPerDay: joi.number().optional(),
- },
- }),
- asyncHandler(createTestData)
-);
-
-export default router;
+export default s.router(devContract, {
+ generateData: {
+ middleware: [onlyAvailableOnDev()],
+ handler: async (r) => callController(DevController.createTestData)(r),
+ },
+});
diff --git a/backend/src/api/routes/docs.ts b/backend/src/api/routes/docs.ts
new file mode 100644
index 000000000000..5892e1eda64f
--- /dev/null
+++ b/backend/src/api/routes/docs.ts
@@ -0,0 +1,49 @@
+import { Response, Router } from "express";
+import * as swaggerUi from "swagger-ui-express";
+import publicSwaggerSpec from "../../documentation/public-swagger.json";
+
+const SWAGGER_UI_OPTIONS = {
+ customCss: ".swagger-ui .topbar { display: none } .try-out { display: none }",
+ customSiteTitle: "Monkeytype API Documentation",
+};
+
+const router = Router();
+
+const root = __dirname + "../../../static";
+
+router.use("/v2/internal", (req, res) => {
+ setCsp(res);
+ res.sendFile("api/internal.html", { root });
+});
+
+router.use("/v2/internal.json", (req, res) => {
+ res.setHeader("Content-Type", "application/json");
+ res.sendFile("api/openapi.json", { root });
+});
+
+router.use(["/v2/public", "/v2/"], (req, res) => {
+ setCsp(res);
+ res.sendFile("api/public.html", { root });
+});
+
+router.use("/v2/public.json", (req, res) => {
+ res.setHeader("Content-Type", "application/json");
+ res.sendFile("api/public.json", { root });
+});
+
+const options = {};
+
+router.use(
+ "/",
+ swaggerUi.serveFiles(publicSwaggerSpec, options),
+ swaggerUi.setup(publicSwaggerSpec, SWAGGER_UI_OPTIONS)
+);
+
+export default router;
+
+function setCsp(res: Response): void {
+ res.setHeader(
+ "Content-Security-Policy",
+ "default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';img-src 'self' monkeytype.com cdn.redoc.ly data:;object-src 'none';script-src 'self' cdn.redoc.ly 'unsafe-inline'; worker-src blob: data;script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests"
+ );
+}
diff --git a/backend/src/api/routes/index.ts b/backend/src/api/routes/index.ts
index 57c613896095..725360033ff8 100644
--- a/backend/src/api/routes/index.ts
+++ b/backend/src/api/routes/index.ts
@@ -1,25 +1,27 @@
import _ from "lodash";
+import { contract } from "@monkeytype/contracts/index";
import psas from "./psas";
import publicStats from "./public";
import users from "./users";
import { join } from "path";
import quotes from "./quotes";
-import configs from "./configs";
import results from "./results";
import presets from "./presets";
import apeKeys from "./ape-keys";
import admin from "./admin";
+import docs from "./docs";
import webhooks from "./webhooks";
import dev from "./dev";
+import configs from "./configs";
import configuration from "./configuration";
import { version } from "../../version";
import leaderboards from "./leaderboards";
import addSwaggerMiddlewares from "./swagger";
import { asyncHandler } from "../../middlewares/utility";
import { MonkeyResponse } from "../../utils/monkey-response";
-import { recordClientVersion } from "../../utils/prometheus";
import {
Application,
+ IRouter,
NextFunction,
Response,
Router,
@@ -28,6 +30,10 @@ import {
import { isDevEnvironment } from "../../utils/misc";
import { getLiveConfiguration } from "../../init/configuration";
import Logger from "../../utils/logger";
+import { createExpressEndpoints, initServer } from "@ts-rest/express";
+import { ZodIssue } from "zod";
+import { MonkeyValidationError } from "@monkeytype/contracts/schemas/api";
+import { authenticateTsRestRequest } from "../../middlewares/auth";
const pathOverride = process.env["API_PATH_OVERRIDE"];
const BASE_ROUTE = pathOverride !== undefined ? `/${pathOverride}` : "";
@@ -35,51 +41,119 @@ const APP_START_TIME = Date.now();
const API_ROUTE_MAP = {
"/users": users,
- "/configs": configs,
- "/results": results,
- "/presets": presets,
- "/psas": psas,
- "/public": publicStats,
- "/leaderboards": leaderboards,
"/quotes": quotes,
- "/ape-keys": apeKeys,
- "/admin": admin,
"/webhooks": webhooks,
+ "/docs": docs,
};
-function addApiRoutes(app: Application): void {
- app.get("/leaderboard", (_req, res) => {
- res.sendStatus(404);
+const s = initServer();
+const router = s.router(contract, {
+ admin,
+ apeKeys,
+ configs,
+ presets,
+ psas,
+ public: publicStats,
+ leaderboards,
+ results,
+ configuration,
+ dev,
+});
+
+export function addApiRoutes(app: Application): void {
+ applyDevApiRoutes(app);
+ applyApiRoutes(app);
+ applyTsRestApiRoutes(app);
+
+ app.use(
+ asyncHandler(async (req, _res) => {
+ return new MonkeyResponse(
+ `Unknown request URL (${req.method}: ${req.path})`,
+ null,
+ 404
+ );
+ })
+ );
+}
+
+function applyTsRestApiRoutes(app: IRouter): void {
+ createExpressEndpoints(contract, router, app, {
+ jsonQuery: true,
+ requestValidationErrorHandler(err, req, res, _next) {
+ let message: string | undefined = undefined;
+ let validationErrors: string[] | undefined = undefined;
+
+ if (err.pathParams?.issues !== undefined) {
+ message = "Invalid path parameter schema";
+ validationErrors = err.pathParams.issues.map(prettyErrorMessage);
+ } else if (err.query?.issues !== undefined) {
+ message = "Invalid query schema";
+ validationErrors = err.query.issues.map(prettyErrorMessage);
+ } else if (err.body?.issues !== undefined) {
+ message = "Invalid request data schema";
+ validationErrors = err.body.issues.map(prettyErrorMessage);
+ } else if (err.headers?.issues !== undefined) {
+ message = "Invalid header schema";
+ validationErrors = err.headers.issues.map(prettyErrorMessage);
+ } else {
+ Logger.error(
+ `Unknown validation error for ${req.method} ${
+ req.path
+ }: ${JSON.stringify(err)}`
+ );
+ res
+ .status(500)
+ .json({ message: "Unknown validation error. Contact support." });
+ return;
+ }
+
+ res
+ .status(422)
+ .json({ message, validationErrors } as MonkeyValidationError);
+ },
+ globalMiddleware: [authenticateTsRestRequest()],
});
+}
+
+function prettyErrorMessage(issue: ZodIssue | undefined): string {
+ if (issue === undefined) return "";
+ const path = issue.path.length > 0 ? `"${issue.path.join(".")}" ` : "";
+ return `${path}${issue.message}`;
+}
+function applyDevApiRoutes(app: Application): void {
if (isDevEnvironment()) {
//disable csp to allow assets to load from unsecured http
app.use((req, res, next) => {
res.setHeader("Content-Security-Policy", "");
- return next();
+ next();
});
app.use("/configure", expressStatic(join(__dirname, "../../../private")));
app.use(async (req, res, next) => {
const slowdown = (await getLiveConfiguration()).dev.responseSlowdownMs;
if (slowdown > 0) {
- Logger.info(`Simulating ${slowdown}ms delay for ${req.path}`);
+ Logger.info(
+ `Simulating ${slowdown}ms delay for ${req.method} ${req.path}`
+ );
await new Promise((resolve) => setTimeout(resolve, slowdown));
}
next();
});
-
- //enable dev edpoints
- app.use("/dev", dev);
}
+}
- // Cannot be added to the route map because it needs to be added before the maintenance handler
- app.use("/configuration", configuration);
-
+function applyApiRoutes(app: Application): void {
addSwaggerMiddlewares(app);
+ //TODO move to globalMiddleware when all endpoints use tsrest
app.use(
(req: MonkeyTypes.Request, res: Response, next: NextFunction): void => {
+ if (req.path.startsWith("/configuration")) {
+ next();
+ return;
+ }
+
const inMaintenance =
process.env["MAINTENANCE"] === "true" ||
req.ctx.configuration.maintenance;
@@ -89,13 +163,6 @@ function addApiRoutes(app: Application): void {
return;
}
- if (req.path === "/psas") {
- const clientVersion =
- (req.headers["x-client-version"] as string) ||
- req.headers["client-version"];
- recordClientVersion(clientVersion?.toString() ?? "unknown");
- }
-
next();
}
);
@@ -110,6 +177,7 @@ function addApiRoutes(app: Application): void {
})
);
+ //legacy route
app.get("/psa", (_req, res) => {
res.json([
{
@@ -124,16 +192,4 @@ function addApiRoutes(app: Application): void {
const apiRoute = `${BASE_ROUTE}${route}`;
app.use(apiRoute, router);
});
-
- app.use(
- asyncHandler(async (req, _res) => {
- return new MonkeyResponse(
- `Unknown request URL (${req.method}: ${req.path})`,
- null,
- 404
- );
- })
- );
}
-
-export default addApiRoutes;
diff --git a/backend/src/api/routes/leaderboards.ts b/backend/src/api/routes/leaderboards.ts
index 9b2705e3aec0..08fffee8eb12 100644
--- a/backend/src/api/routes/leaderboards.ts
+++ b/backend/src/api/routes/leaderboards.ts
@@ -1,41 +1,11 @@
-import joi from "joi";
-import { Router } from "express";
-import * as RateLimit from "../../middlewares/rate-limit";
+import { initServer } from "@ts-rest/express";
import { withApeRateLimiter } from "../../middlewares/ape-rate-limit";
-import { authenticateRequest } from "../../middlewares/auth";
-import * as LeaderboardController from "../controllers/leaderboard";
import { validate } from "../../middlewares/configuration";
-import { validateRequest } from "../../middlewares/validation";
-import { asyncHandler } from "../../middlewares/utility";
-
-const BASE_LEADERBOARD_VALIDATION_SCHEMA = {
- language: joi
- .string()
- .max(50)
- .pattern(/^[a-zA-Z0-9_+]+$/)
- .required(),
- mode: joi
- .string()
- .valid("time", "words", "quote", "zen", "custom")
- .required(),
- mode2: joi
- .string()
- .regex(/^(\d)+|custom|zen/)
- .required(),
-};
-
-const LEADERBOARD_VALIDATION_SCHEMA_WITH_LIMIT = {
- ...BASE_LEADERBOARD_VALIDATION_SCHEMA,
- skip: joi.number().min(0),
- limit: joi.number().min(0).max(50),
-};
-
-const DAILY_LEADERBOARD_VALIDATION_SCHEMA = {
- ...LEADERBOARD_VALIDATION_SCHEMA_WITH_LIMIT,
- daysBefore: joi.number().min(1).max(1),
-};
+import * as RateLimit from "../../middlewares/rate-limit";
+import * as LeaderboardController from "../controllers/leaderboard";
-const router = Router();
+import { leaderboardsContract } from "@monkeytype/contracts/leaderboards";
+import { callController } from "../ts-rest-adapter";
const requireDailyLeaderboardsEnabled = validate({
criteria: (configuration) => {
@@ -44,58 +14,6 @@ const requireDailyLeaderboardsEnabled = validate({
invalidMessage: "Daily leaderboards are not available at this time.",
});
-router.get(
- "/",
- authenticateRequest({ isPublic: true }),
- withApeRateLimiter(RateLimit.leaderboardsGet),
- validateRequest({
- query: LEADERBOARD_VALIDATION_SCHEMA_WITH_LIMIT,
- }),
- asyncHandler(LeaderboardController.getLeaderboard)
-);
-
-router.get(
- "/rank",
- authenticateRequest({ acceptApeKeys: true }),
- withApeRateLimiter(RateLimit.leaderboardsGet),
- validateRequest({
- query: BASE_LEADERBOARD_VALIDATION_SCHEMA,
- }),
- asyncHandler(LeaderboardController.getRankFromLeaderboard)
-);
-
-router.get(
- "/daily",
- requireDailyLeaderboardsEnabled,
- authenticateRequest({ isPublic: true }),
- RateLimit.leaderboardsGet,
- validateRequest({
- query: DAILY_LEADERBOARD_VALIDATION_SCHEMA,
- }),
- asyncHandler(LeaderboardController.getDailyLeaderboard)
-);
-
-router.get(
- "/daily/rank",
- requireDailyLeaderboardsEnabled,
- authenticateRequest(),
- RateLimit.leaderboardsGet,
- validateRequest({
- query: DAILY_LEADERBOARD_VALIDATION_SCHEMA,
- }),
- asyncHandler(LeaderboardController.getDailyLeaderboardRank)
-);
-
-const BASE_XP_LEADERBOARD_VALIDATION_SCHEMA = {
- skip: joi.number().min(0),
- limit: joi.number().min(0).max(50),
-};
-
-const WEEKLY_XP_LEADERBOARD_VALIDATION_SCHEMA = {
- ...BASE_XP_LEADERBOARD_VALIDATION_SCHEMA,
- weeksBefore: joi.number().min(1).max(1),
-};
-
const requireWeeklyXpLeaderboardEnabled = validate({
criteria: (configuration) => {
return configuration.leaderboards.weeklyXp.enabled;
@@ -103,23 +21,36 @@ const requireWeeklyXpLeaderboardEnabled = validate({
invalidMessage: "Weekly XP leaderboards are not available at this time.",
});
-router.get(
- "/xp/weekly",
- requireWeeklyXpLeaderboardEnabled,
- authenticateRequest({ isPublic: true }),
- withApeRateLimiter(RateLimit.leaderboardsGet),
- validateRequest({
- query: WEEKLY_XP_LEADERBOARD_VALIDATION_SCHEMA,
- }),
- asyncHandler(LeaderboardController.getWeeklyXpLeaderboardResults)
-);
-
-router.get(
- "/xp/weekly/rank",
- requireWeeklyXpLeaderboardEnabled,
- authenticateRequest(),
- withApeRateLimiter(RateLimit.leaderboardsGet),
- asyncHandler(LeaderboardController.getWeeklyXpLeaderboardRank)
-);
-
-export default router;
+const s = initServer();
+export default s.router(leaderboardsContract, {
+ get: {
+ middleware: [RateLimit.leaderboardsGet],
+ handler: async (r) =>
+ callController(LeaderboardController.getLeaderboard)(r),
+ },
+ getRank: {
+ middleware: [withApeRateLimiter(RateLimit.leaderboardsGet)],
+ handler: async (r) =>
+ callController(LeaderboardController.getRankFromLeaderboard)(r),
+ },
+ getDaily: {
+ middleware: [requireDailyLeaderboardsEnabled, RateLimit.leaderboardsGet],
+ handler: async (r) =>
+ callController(LeaderboardController.getDailyLeaderboard)(r),
+ },
+ getDailyRank: {
+ middleware: [requireDailyLeaderboardsEnabled, RateLimit.leaderboardsGet],
+ handler: async (r) =>
+ callController(LeaderboardController.getDailyLeaderboardRank)(r),
+ },
+ getWeeklyXp: {
+ middleware: [requireWeeklyXpLeaderboardEnabled, RateLimit.leaderboardsGet],
+ handler: async (r) =>
+ callController(LeaderboardController.getWeeklyXpLeaderboardResults)(r),
+ },
+ getWeeklyXpRank: {
+ middleware: [requireWeeklyXpLeaderboardEnabled, RateLimit.leaderboardsGet],
+ handler: async (r) =>
+ callController(LeaderboardController.getWeeklyXpLeaderboardRank)(r),
+ },
+});
diff --git a/backend/src/api/routes/presets.ts b/backend/src/api/routes/presets.ts
index c33ce3afe99b..a995cd977a2b 100644
--- a/backend/src/api/routes/presets.ts
+++ b/backend/src/api/routes/presets.ts
@@ -1,74 +1,25 @@
-import joi from "joi";
-import { authenticateRequest } from "../../middlewares/auth";
-import * as PresetController from "../controllers/preset";
+import { presetsContract } from "@monkeytype/contracts/presets";
+import { initServer } from "@ts-rest/express";
import * as RateLimit from "../../middlewares/rate-limit";
-import configSchema from "../schemas/config-schema";
-import { Router } from "express";
-import { asyncHandler } from "../../middlewares/utility";
-import { validateRequest } from "../../middlewares/validation";
-
-const router = Router();
-
-const presetNameSchema = joi
- .string()
- .required()
- .regex(/^[0-9a-zA-Z_-]+$/)
- .max(16)
- .messages({
- "string.pattern.base": "Invalid preset name",
- "string.max": "Preset name exceeds maximum of 16 characters",
- });
-
-router.get(
- "/",
- authenticateRequest(),
- RateLimit.presetsGet,
- asyncHandler(PresetController.getPresets)
-);
-
-router.post(
- "/",
- authenticateRequest(),
- RateLimit.presetsAdd,
- validateRequest({
- body: {
- name: presetNameSchema,
- config: configSchema.keys({
- tags: joi.array().items(joi.string().token().max(50)),
- }),
- },
- }),
- asyncHandler(PresetController.addPreset)
-);
-
-router.patch(
- "/",
- authenticateRequest(),
- RateLimit.presetsEdit,
- validateRequest({
- body: {
- _id: joi.string().token().required(),
- name: presetNameSchema,
- config: configSchema
- .keys({
- tags: joi.array().items(joi.string().token().max(50)),
- })
- .allow(null),
- },
- }),
- asyncHandler(PresetController.editPreset)
-);
-
-router.delete(
- "/:presetId",
- authenticateRequest(),
- RateLimit.presetsRemove,
- validateRequest({
- params: {
- presetId: joi.string().token().required(),
- },
- }),
- asyncHandler(PresetController.removePreset)
-);
+import * as PresetController from "../controllers/preset";
+import { callController } from "../ts-rest-adapter";
-export default router;
+const s = initServer();
+export default s.router(presetsContract, {
+ get: {
+ middleware: [RateLimit.presetsGet],
+ handler: async (r) => callController(PresetController.getPresets)(r),
+ },
+ add: {
+ middleware: [RateLimit.presetsAdd],
+ handler: async (r) => callController(PresetController.addPreset)(r),
+ },
+ save: {
+ middleware: [RateLimit.presetsEdit],
+ handler: async (r) => callController(PresetController.editPreset)(r),
+ },
+ delete: {
+ middleware: [RateLimit.presetsRemove],
+ handler: async (r) => callController(PresetController.removePreset)(r),
+ },
+});
diff --git a/backend/src/api/routes/psas.ts b/backend/src/api/routes/psas.ts
index 6fbbd67598bd..093b4c870e06 100644
--- a/backend/src/api/routes/psas.ts
+++ b/backend/src/api/routes/psas.ts
@@ -1,10 +1,14 @@
-import { Router } from "express";
-import * as PsaController from "../controllers/psa";
+import { psasContract } from "@monkeytype/contracts/psas";
+import { initServer } from "@ts-rest/express";
import * as RateLimit from "../../middlewares/rate-limit";
-import { asyncHandler } from "../../middlewares/utility";
-
-const router = Router();
-
-router.get("/", RateLimit.psaGet, asyncHandler(PsaController.getPsas));
+import * as PsaController from "../controllers/psa";
+import { callController } from "../ts-rest-adapter";
+import { recordClientVersion } from "../../middlewares/utility";
-export default router;
+const s = initServer();
+export default s.router(psasContract, {
+ get: {
+ middleware: [recordClientVersion(), RateLimit.psaGet],
+ handler: async (r) => callController(PsaController.getPsas)(r),
+ },
+});
diff --git a/backend/src/api/routes/public.ts b/backend/src/api/routes/public.ts
index 86491c6b346a..b5e310fb72e4 100644
--- a/backend/src/api/routes/public.ts
+++ b/backend/src/api/routes/public.ts
@@ -1,41 +1,17 @@
-import { Router } from "express";
-import * as PublicController from "../controllers/public";
+import { publicContract } from "@monkeytype/contracts/public";
+import { initServer } from "@ts-rest/express";
import * as RateLimit from "../../middlewares/rate-limit";
-import { asyncHandler } from "../../middlewares/utility";
-import joi from "joi";
-import { validateRequest } from "../../middlewares/validation";
-
-const GET_MODE_STATS_VALIDATION_SCHEMA = {
- language: joi
- .string()
- .max(50)
- .pattern(/^[a-zA-Z0-9_+]+$/)
- .required(),
- mode: joi
- .string()
- .valid("time", "words", "quote", "zen", "custom")
- .required(),
- mode2: joi
- .string()
- .regex(/^(\d)+|custom|zen/)
- .required(),
-};
-
-const router = Router();
-
-router.get(
- "/speedHistogram",
- RateLimit.publicStatsGet,
- validateRequest({
- query: GET_MODE_STATS_VALIDATION_SCHEMA,
- }),
- asyncHandler(PublicController.getPublicSpeedHistogram)
-);
-
-router.get(
- "/typingStats",
- RateLimit.publicStatsGet,
- asyncHandler(PublicController.getPublicTypingStats)
-);
+import * as PublicController from "../controllers/public";
+import { callController } from "../ts-rest-adapter";
-export default router;
+const s = initServer();
+export default s.router(publicContract, {
+ getSpeedHistogram: {
+ middleware: [RateLimit.publicStatsGet],
+ handler: async (r) => callController(PublicController.getSpeedHistogram)(r),
+ },
+ getTypingStats: {
+ middleware: [RateLimit.publicStatsGet],
+ handler: async (r) => callController(PublicController.getTypingStats)(r),
+ },
+});
diff --git a/backend/src/api/routes/quotes.ts b/backend/src/api/routes/quotes.ts
index 19a4f760461d..79784b5aff8e 100644
--- a/backend/src/api/routes/quotes.ts
+++ b/backend/src/api/routes/quotes.ts
@@ -10,7 +10,7 @@ import { validateRequest } from "../../middlewares/validation";
const router = Router();
-const checkIfUserIsQuoteMod = checkUserPermissions({
+const checkIfUserIsQuoteMod = checkUserPermissions(["quoteMod"], {
criteria: (user) => {
return (
user.quoteMod === true ||
@@ -171,7 +171,7 @@ router.post(
captcha: withCustomMessages.regex(/[\w-_]+/).required(),
},
}),
- checkUserPermissions({
+ checkUserPermissions(["canReport"], {
criteria: (user) => {
return user.canReport !== false;
},
diff --git a/backend/src/api/routes/results.ts b/backend/src/api/routes/results.ts
index 92328c072ffd..d8d8dfaa0f5c 100644
--- a/backend/src/api/routes/results.ts
+++ b/backend/src/api/routes/results.ts
@@ -1,85 +1,40 @@
-import * as ResultController from "../controllers/result";
-import resultSchema from "../schemas/result-schema";
-import * as RateLimit from "../../middlewares/rate-limit";
-import { Router } from "express";
-import { authenticateRequest } from "../../middlewares/auth";
-import joi from "joi";
-import { withApeRateLimiter } from "../../middlewares/ape-rate-limit";
-import { validateRequest } from "../../middlewares/validation";
-import { asyncHandler } from "../../middlewares/utility";
+import { resultsContract } from "@monkeytype/contracts/results";
+import { initServer } from "@ts-rest/express";
+import { withApeRateLimiter2 as withApeRateLimiter } from "../../middlewares/ape-rate-limit";
import { validate } from "../../middlewares/configuration";
+import * as RateLimit from "../../middlewares/rate-limit";
+import * as ResultController from "../controllers/result";
+import { callController } from "../ts-rest-adapter";
-const router = Router();
-
-router.get(
- "/",
- authenticateRequest({
- acceptApeKeys: true,
- }),
- withApeRateLimiter(RateLimit.resultsGet, RateLimit.resultsGetApe),
- validateRequest({
- query: {
- onOrAfterTimestamp: joi.number().integer().min(1589428800000),
- limit: joi.number().integer().min(0).max(1000),
- offset: joi.number().integer().min(0),
- },
- }),
- asyncHandler(ResultController.getResults)
-);
-
-router.post(
- "/",
- validate({
- criteria: (configuration) => {
- return configuration.results.savingEnabled;
- },
- invalidMessage: "Results are not being saved at this time.",
- }),
- authenticateRequest(),
- RateLimit.resultsAdd,
- validateRequest({
- body: {
- result: resultSchema,
- },
- }),
- asyncHandler(ResultController.addResult)
-);
-
-router.patch(
- "/tags",
- authenticateRequest(),
- RateLimit.resultsTagsUpdate,
- validateRequest({
- body: {
- tagIds: joi
- .array()
- .items(joi.string().regex(/^[a-f\d]{24}$/i))
- .required(),
- resultId: joi
- .string()
- .regex(/^[a-f\d]{24}$/i)
- .required(),
- },
- }),
- asyncHandler(ResultController.updateTags)
-);
-
-router.delete(
- "/",
- authenticateRequest({
- requireFreshToken: true,
- }),
- RateLimit.resultsDeleteAll,
- asyncHandler(ResultController.deleteAll)
-);
-
-router.get(
- "/last",
- authenticateRequest({
- acceptApeKeys: true,
- }),
- withApeRateLimiter(RateLimit.resultsGet),
- asyncHandler(ResultController.getLastResult)
-);
+const validateResultSavingEnabled = validate({
+ criteria: (configuration) => {
+ return configuration.results.savingEnabled;
+ },
+ invalidMessage: "Results are not being saved at this time.",
+});
-export default router;
+const s = initServer();
+export default s.router(resultsContract, {
+ get: {
+ middleware: [
+ withApeRateLimiter(RateLimit.resultsGet, RateLimit.resultsGetApe),
+ ],
+ handler: async (r) => callController(ResultController.getResults)(r),
+ },
+ add: {
+ middleware: [validateResultSavingEnabled, RateLimit.resultsTagsUpdate],
+ handler: async (r) => callController(ResultController.addResult)(r),
+ },
+ updateTags: {
+ middleware: [RateLimit.resultsTagsUpdate],
+ handler: async (r) => callController(ResultController.updateTags)(r),
+ },
+ deleteAll: {
+ middleware: [RateLimit.resultsDeleteAll],
+ handler: async (r) => callController(ResultController.deleteAll)(r),
+ },
+ getLast: {
+ middleware: [withApeRateLimiter(RateLimit.resultsGet)],
+ handler: async (r) => callController(ResultController.getLastResult)(r),
+ },
+});
diff --git a/backend/src/api/routes/swagger.ts b/backend/src/api/routes/swagger.ts
index 37d4754b8e5d..e9fb5adb74e2 100644
--- a/backend/src/api/routes/swagger.ts
+++ b/backend/src/api/routes/swagger.ts
@@ -1,19 +1,8 @@
-import _ from "lodash";
import { Application } from "express";
import { getMiddleware as getSwaggerMiddleware } from "swagger-stats";
-import {
- serve as serveSwagger,
- setup as setupSwaggerUi,
-} from "swagger-ui-express";
-import publicSwaggerSpec from "../../documentation/public-swagger.json";
import internalSwaggerSpec from "../../documentation/internal-swagger.json";
import { isDevEnvironment } from "../../utils/misc";
-const SWAGGER_UI_OPTIONS = {
- customCss: ".swagger-ui .topbar { display: none } .try-out { display: none }",
- customSiteTitle: "Monkeytype API Documentation",
-};
-
function addSwaggerMiddlewares(app: Application): void {
app.use(
getSwaggerMiddleware({
@@ -30,12 +19,6 @@ function addSwaggerMiddlewares(app: Application): void {
},
})
);
-
- app.use(
- ["/documentation", "/docs"],
- serveSwagger,
- setupSwaggerUi(publicSwaggerSpec, SWAGGER_UI_OPTIONS)
- );
}
export default addSwaggerMiddlewares;
diff --git a/backend/src/api/routes/users.ts b/backend/src/api/routes/users.ts
index 79e5d0658b71..1febadf8d6fb 100644
--- a/backend/src/api/routes/users.ts
+++ b/backend/src/api/routes/users.ts
@@ -75,7 +75,7 @@ const usernameValidation = joi
return helpers.error("string.pattern.base");
}
- return value;
+ return value as string;
})
.messages({
"string.profanity":
@@ -217,7 +217,7 @@ router.patch(
RateLimit.userUpdateEmail,
validateRequest({
body: {
- newPassword: joi.string().required(),
+ newPassword: joi.string().min(6).required(),
},
}),
asyncHandler(UserController.updatePassword)
@@ -537,7 +537,7 @@ const profileDetailsBase = joi
return helpers.error("string.profanity");
}
- return value;
+ return value as string;
})
.messages({
"string.profanity":
@@ -638,7 +638,7 @@ router.post(
captcha: withCustomMessages.regex(/[\w-_]+/).required(),
},
}),
- checkUserPermissions({
+ checkUserPermissions(["canReport"], {
criteria: (user) => {
return user.canReport !== false;
},
diff --git a/backend/src/api/schemas/config-schema.ts b/backend/src/api/schemas/config-schema.ts
deleted file mode 100644
index baebe8b71518..000000000000
--- a/backend/src/api/schemas/config-schema.ts
+++ /dev/null
@@ -1,152 +0,0 @@
-import _ from "lodash";
-import joi from "joi";
-
-const CARET_STYLES = [
- "off",
- "default",
- "underline",
- "outline",
- "block",
- "carrot",
- "banana",
-];
-
-const CONFIG_SCHEMA = joi.object({
- theme: joi.string().max(50).token(),
- themeLight: joi.string().max(50).token(),
- themeDark: joi.string().max(50).token(),
- autoSwitchTheme: joi.boolean(),
- customTheme: joi.boolean(),
- customThemeId: joi.string().min(0).max(24).token(),
- customThemeColors: joi
- .array()
- .items(joi.string().pattern(/^#([\da-f]{3}){1,2}$/i))
- .length(10),
- favThemes: joi.array().items(joi.string().max(50).token()),
- showKeyTips: joi.boolean(),
- smoothCaret: joi.string().valid("off", "slow", "medium", "fast"),
- quickRestart: joi.string().valid("off", "tab", "esc", "enter"),
- punctuation: joi.boolean(),
- numbers: joi.boolean(),
- words: joi.number().min(0),
- time: joi.number().min(0),
- mode: joi.string().valid("time", "words", "quote", "zen", "custom"),
- quoteLength: joi.array().items(joi.number()),
- language: joi
- .string()
- .max(50)
- .pattern(/^[a-zA-Z0-9_+]+$/),
- fontSize: joi.number().min(0),
- freedomMode: joi.boolean(),
- difficulty: joi.string().valid("normal", "expert", "master"),
- blindMode: joi.boolean(),
- quickEnd: joi.boolean(),
- caretStyle: joi.string().valid(...CARET_STYLES),
- paceCaretStyle: joi.string().valid(...CARET_STYLES),
- flipTestColors: joi.boolean(),
- layout: joi.string().max(50).token(),
- funbox: joi
- .string()
- .max(100)
- .regex(/[\w#]+/),
- confidenceMode: joi.string().valid("off", "on", "max"),
- indicateTypos: joi.string().valid("off", "below", "replace"),
- timerStyle: joi.string().valid("off", "bar", "text", "mini"),
- liveSpeedStyle: joi.string().valid("off", "text", "mini"),
- liveAccStyle: joi.string().valid("off", "text", "mini"),
- liveBurstStyle: joi.string().valid("off", "text", "mini"),
- colorfulMode: joi.boolean(),
- randomTheme: joi
- .string()
- .valid("off", "on", "fav", "light", "dark", "custom"),
- timerColor: joi.string().valid("black", "sub", "text", "main"),
- timerOpacity: joi.number().valid(0.25, 0.5, 0.75, 1),
- stopOnError: joi.string().valid("off", "word", "letter"),
- showAllLines: joi.boolean(),
- keymapMode: joi.string().valid("off", "static", "react", "next"),
- keymapStyle: joi
- .string()
- .valid(
- "staggered",
- "alice",
- "matrix",
- "split",
- "split_matrix",
- "steno",
- "steno_matrix"
- ),
- keymapLegendStyle: joi
- .string()
- .valid("lowercase", "uppercase", "blank", "dynamic"),
- keymapLayout: joi
- .string()
- .regex(/[\w-_]+/)
- .valid()
- .max(50),
- keymapShowTopRow: joi.string().valid("always", "layout", "never"),
- fontFamily: joi
- .string()
- .max(50)
- .regex(/^[a-zA-Z0-9_\-+.]+$/),
- smoothLineScroll: joi.boolean(),
- alwaysShowDecimalPlaces: joi.boolean(),
- alwaysShowWordsHistory: joi.boolean(),
- singleListCommandLine: joi.string().valid("manual", "on"),
- capsLockWarning: joi.boolean(),
- playSoundOnError: joi.string().valid("off", ..._.range(1, 5).map(_.toString)),
- playSoundOnClick: joi.alternatives().try(
- joi.boolean(), //todo remove soon
- joi.string().valid("off", ..._.range(1, 16).map(_.toString))
- ),
- soundVolume: joi.string().valid("0.1", "0.5", "1.0"),
- startGraphsAtZero: joi.boolean(),
- showOutOfFocusWarning: joi.boolean(),
- paceCaret: joi
- .string()
- .valid("off", "average", "pb", "last", "daily", "custom"),
- paceCaretCustomSpeed: joi.number().min(0),
- repeatedPace: joi.boolean(),
- accountChart: joi
- .array()
- .items(joi.string().valid("on", "off"))
- .min(3)
- .max(4)
- .optional(), //replace min max with length 4 after a while
- minWpm: joi.string().valid("off", "custom"),
- minWpmCustomSpeed: joi.number().min(0),
- highlightMode: joi
- .string()
- .valid(
- "off",
- "letter",
- "word",
- "next_word",
- "next_two_words",
- "next_three_words"
- ),
- tapeMode: joi.string().valid("off", "letter", "word"),
- typingSpeedUnit: joi.string().valid("wpm", "cpm", "wps", "cps", "wph"),
- enableAds: joi.string().valid("off", "on", "max"),
- ads: joi.string().valid("off", "result", "on", "sellout"),
- hideExtraLetters: joi.boolean(),
- strictSpace: joi.boolean(),
- minAcc: joi.string().valid("off", "custom"),
- minAccCustom: joi.number().min(0),
- monkey: joi.boolean(),
- repeatQuotes: joi.string().valid("off", "typing"),
- oppositeShiftMode: joi.string().valid("off", "on", "keymap"),
- customBackground: joi.string().uri().allow(""),
- customBackgroundSize: joi.string().valid("cover", "contain", "max"),
- customBackgroundFilter: joi.array().items(joi.number()),
- customLayoutfluid: joi.string().regex(/^[0-9a-zA-Z_#]+$/),
- monkeyPowerLevel: joi.string().valid("off", "1", "2", "3", "4"),
- minBurst: joi.string().valid("off", "fixed", "flex"),
- minBurstCustomSpeed: joi.number().min(0),
- burstHeatmap: joi.boolean(),
- britishEnglish: joi.boolean(),
- lazyMode: joi.boolean(),
- showAverage: joi.string().valid("off", "speed", "acc", "both"),
- maxLineWidth: joi.number().min(20).max(1000).allow(0),
-});
-
-export default CONFIG_SCHEMA;
diff --git a/backend/src/api/schemas/result-schema.ts b/backend/src/api/schemas/result-schema.ts
deleted file mode 100644
index 233469922db1..000000000000
--- a/backend/src/api/schemas/result-schema.ts
+++ /dev/null
@@ -1,98 +0,0 @@
-import joi from "joi";
-
-const RESULT_SCHEMA = joi
- .object({
- acc: joi.number().min(50).max(100).required(),
- afkDuration: joi.number().min(0).required(),
- bailedOut: joi.boolean().required(),
- blindMode: joi.boolean().required(),
- challenge: joi.string().max(100).token(),
- charStats: joi.array().items(joi.number().min(0)).length(4).required(),
- charTotal: joi.number().min(0),
- chartData: joi
- .alternatives()
- .try(
- joi.object({
- wpm: joi.array().max(122).items(joi.number().min(0)).required(),
- raw: joi.array().max(122).items(joi.number().min(0)).required(),
- err: joi.array().max(122).items(joi.number().min(0)).required(),
- }),
- joi.string().valid("toolong")
- )
- .required(),
- consistency: joi.number().min(0).max(100).required(),
- customText: joi.object({
- textLen: joi.number().required(),
- mode: joi.string().valid("repeat", "random", "shuffle").required(),
- pipeDelimiter: joi.boolean().required(),
- limit: joi.object({
- mode: joi.string().valid("word", "time", "section").required(),
- value: joi.number().min(0).required(),
- }),
- }),
- difficulty: joi.string().valid("normal", "expert", "master").required(),
- funbox: joi
- .string()
- .max(100)
- .regex(/[\w#]+/)
- .required(),
- hash: joi.string().max(100).token().required(),
- incompleteTestSeconds: joi.number().min(0).required(),
- incompleteTests: joi
- .array()
- .items(
- joi.object({
- acc: joi.number().min(0).max(100).required(),
- seconds: joi.number().min(0).required(),
- })
- )
- .required(),
- keyConsistency: joi.number().min(0).max(100).required(),
- keyDuration: joi
- .alternatives()
- .try(
- joi.array().items(joi.number().min(0)),
- joi.string().valid("toolong")
- ),
- keySpacing: joi
- .alternatives()
- .try(
- joi.array().items(joi.number().min(0)),
- joi.string().valid("toolong")
- ),
- keyOverlap: joi.number().min(0),
- lastKeyToEnd: joi.number().min(0),
- startToFirstKey: joi.number().min(0),
- language: joi
- .string()
- .max(100)
- .regex(/[\w+]+/)
- .required(),
- lazyMode: joi.boolean().required(),
- mode: joi
- .string()
- .valid("time", "words", "quote", "zen", "custom")
- .required(),
- mode2: joi
- .string()
- .regex(/^(\d)+|custom|zen/)
- .required(),
- numbers: joi.boolean().required(),
- punctuation: joi.boolean().required(),
- quoteLength: joi.number().min(0).max(3),
- rawWpm: joi.number().min(0).max(420).required(),
- restartCount: joi.number().required(),
- tags: joi
- .array()
- .items(joi.string().regex(/^[a-f\d]{24}$/i))
- .required(),
- testDuration: joi.number().required().min(1),
- timestamp: joi.date().timestamp().required(),
- uid: joi.string().max(100).token().required(),
- wpm: joi.number().min(0).max(420).required(),
- wpmConsistency: joi.number().min(0).max(100).required(),
- stopOnLetter: joi.boolean().required(),
- })
- .required();
-
-export default RESULT_SCHEMA;
diff --git a/backend/src/api/ts-rest-adapter.ts b/backend/src/api/ts-rest-adapter.ts
new file mode 100644
index 000000000000..dba79b095f04
--- /dev/null
+++ b/backend/src/api/ts-rest-adapter.ts
@@ -0,0 +1,75 @@
+import { AppRoute, AppRouter } from "@ts-rest/core";
+import { TsRestRequest } from "@ts-rest/express";
+import { MonkeyResponse2 } from "../utils/monkey-response";
+export function callController<
+ TRoute extends AppRoute | AppRouter,
+ TQuery,
+ TBody,
+ TParams,
+ TResponse,
+ //ignoring as it might be used in the future
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
+ TStatus = 200
+>(
+ handler: Handler
+): (all: RequestType2) => Promise<{
+ status: TStatus;
+ body: { message: string; data: TResponse };
+}> {
+ return async (all) => {
+ const req: MonkeyTypes.Request2 = {
+ body: all.body as TBody,
+ query: all.query as TQuery,
+ params: all.params as TParams,
+ raw: all.req,
+ ctx: all.req["ctx"],
+ };
+
+ const result = await handler(req);
+ const response = {
+ status: 200 as TStatus,
+ body: {
+ message: result.message,
+ data: result.data,
+ },
+ };
+
+ return response;
+ };
+}
+
+type WithBody = {
+ body: T;
+};
+type WithQuery = {
+ query: T;
+};
+
+type WithParams = {
+ params: T;
+};
+
+type WithoutBody = {
+ body?: never;
+};
+type WithoutQuery = {
+ query?: never;
+};
+type WithoutParams = {
+ params?: never;
+};
+
+type Handler = (
+ req: MonkeyTypes.Request2
+) => Promise>;
+
+type RequestType2<
+ TRoute extends AppRoute | AppRouter,
+ TQuery,
+ TBody,
+ TParams
+> = {
+ req: TsRestRequest;
+} & (TQuery extends undefined ? WithoutQuery : WithQuery) &
+ (TBody extends undefined ? WithoutBody : WithBody) &
+ (TParams extends undefined ? WithoutParams : WithParams);
diff --git a/backend/src/app.ts b/backend/src/app.ts
index 2a54709085d6..6c85bd10c28a 100644
--- a/backend/src/app.ts
+++ b/backend/src/app.ts
@@ -1,6 +1,6 @@
import cors from "cors";
import helmet from "helmet";
-import addApiRoutes from "./api/routes";
+import { addApiRoutes } from "./api/routes";
import express, { urlencoded, json } from "express";
import contextMiddleware from "./middlewares/context";
import errorHandlingMiddleware from "./middlewares/error";
diff --git a/backend/src/constants/base-configuration.ts b/backend/src/constants/base-configuration.ts
index 5ae74dfb9dd7..6e5c44a8e8dc 100644
--- a/backend/src/constants/base-configuration.ts
+++ b/backend/src/constants/base-configuration.ts
@@ -1,9 +1,11 @@
+import { Configuration } from "@monkeytype/contracts/schemas/configuration";
+
/**
* This is the base schema for the configuration of the API backend.
* To add a new configuration. Simply add it to this object.
* When changing this template, please follow the principle of "Secure by default" (https://en.wikipedia.org/wiki/Secure_by_default).
*/
-export const BASE_CONFIGURATION: SharedTypes.Configuration = {
+export const BASE_CONFIGURATION: Configuration = {
maintenance: false,
dev: {
responseSlowdownMs: 0,
@@ -145,449 +147,447 @@ type Schema = {
: never;
};
-export const CONFIGURATION_FORM_SCHEMA: ObjectSchema =
- {
- type: "object",
- label: "Server Configuration",
- fields: {
- maintenance: {
- type: "boolean",
- label: "In Maintenance",
- },
- dev: {
- type: "object",
- label: "Development",
- fields: {
- responseSlowdownMs: {
- type: "number",
- label: "Response Slowdown (miliseconds)",
- min: 0,
- },
+export const CONFIGURATION_FORM_SCHEMA: ObjectSchema = {
+ type: "object",
+ label: "Server Configuration",
+ fields: {
+ maintenance: {
+ type: "boolean",
+ label: "In Maintenance",
+ },
+ dev: {
+ type: "object",
+ label: "Development",
+ fields: {
+ responseSlowdownMs: {
+ type: "number",
+ label: "Response Slowdown (miliseconds)",
+ min: 0,
},
},
- results: {
- type: "object",
- label: "Results",
- fields: {
- savingEnabled: {
- type: "boolean",
- label: "Saving Results",
- },
- objectHashCheckEnabled: {
- type: "boolean",
- label: "Object Hash Check",
- },
- filterPresets: {
- type: "object",
- label: "Filter Presets",
- fields: {
- enabled: {
- type: "boolean",
- label: "Enabled",
- },
- maxPresetsPerUser: {
- type: "number",
- label: "Max Presets Per User",
- min: 0,
- },
+ },
+ results: {
+ type: "object",
+ label: "Results",
+ fields: {
+ savingEnabled: {
+ type: "boolean",
+ label: "Saving Results",
+ },
+ objectHashCheckEnabled: {
+ type: "boolean",
+ label: "Object Hash Check",
+ },
+ filterPresets: {
+ type: "object",
+ label: "Filter Presets",
+ fields: {
+ enabled: {
+ type: "boolean",
+ label: "Enabled",
},
- },
- limits: {
- type: "object",
- label: "maximum results",
- fields: {
- regularUser: {
- type: "number",
- label: "for regular users",
- min: 0,
- },
- premiumUser: {
- type: "number",
- label: "for premium users",
- min: 0,
- },
+ maxPresetsPerUser: {
+ type: "number",
+ label: "Max Presets Per User",
+ min: 0,
},
},
- maxBatchSize: {
- type: "number",
- label: "results endpoint max batch size",
- min: 1,
+ },
+ limits: {
+ type: "object",
+ label: "maximum results",
+ fields: {
+ regularUser: {
+ type: "number",
+ label: "for regular users",
+ min: 0,
+ },
+ premiumUser: {
+ type: "number",
+ label: "for premium users",
+ min: 0,
+ },
},
},
+ maxBatchSize: {
+ type: "number",
+ label: "results endpoint max batch size",
+ min: 1,
+ },
},
- quotes: {
- type: "object",
- label: "Quotes",
- fields: {
- reporting: {
- type: "object",
- label: "Reporting",
- fields: {
- enabled: {
- type: "boolean",
- label: "Enabled",
- },
- maxReports: {
- type: "number",
- label: "Max Reports",
- },
- contentReportLimit: {
- type: "number",
- label: "Content Report Limit",
- },
+ },
+ quotes: {
+ type: "object",
+ label: "Quotes",
+ fields: {
+ reporting: {
+ type: "object",
+ label: "Reporting",
+ fields: {
+ enabled: {
+ type: "boolean",
+ label: "Enabled",
+ },
+ maxReports: {
+ type: "number",
+ label: "Max Reports",
+ },
+ contentReportLimit: {
+ type: "number",
+ label: "Content Report Limit",
},
},
- submissionsEnabled: {
- type: "boolean",
- label: "Submissions Enabled",
- },
- maxFavorites: {
- type: "number",
- label: "Max Favorites",
- },
+ },
+ submissionsEnabled: {
+ type: "boolean",
+ label: "Submissions Enabled",
+ },
+ maxFavorites: {
+ type: "number",
+ label: "Max Favorites",
},
},
- admin: {
- type: "object",
- label: "Admin",
- fields: {
- endpointsEnabled: {
- type: "boolean",
- label: "Endpoints Enabled",
- },
+ },
+ admin: {
+ type: "object",
+ label: "Admin",
+ fields: {
+ endpointsEnabled: {
+ type: "boolean",
+ label: "Endpoints Enabled",
},
},
- apeKeys: {
- type: "object",
- label: "Ape Keys",
- fields: {
- endpointsEnabled: {
- type: "boolean",
- label: "Endpoints Enabled",
- },
- acceptKeys: {
- type: "boolean",
- label: "Accept Keys",
- },
- maxKeysPerUser: {
- type: "number",
- label: "Max Keys Per User",
- min: 0,
- },
- apeKeyBytes: {
- type: "number",
- label: "Ape Key Bytes",
- min: 24,
+ },
+ apeKeys: {
+ type: "object",
+ label: "Ape Keys",
+ fields: {
+ endpointsEnabled: {
+ type: "boolean",
+ label: "Endpoints Enabled",
+ },
+ acceptKeys: {
+ type: "boolean",
+ label: "Accept Keys",
+ },
+ maxKeysPerUser: {
+ type: "number",
+ label: "Max Keys Per User",
+ min: 0,
+ },
+ apeKeyBytes: {
+ type: "number",
+ label: "Ape Key Bytes",
+ min: 24,
+ },
+ apeKeySaltRounds: {
+ type: "number",
+ label: "Ape Key Salt Rounds",
+ min: 5,
+ },
+ },
+ },
+ users: {
+ type: "object",
+ label: "Users",
+ fields: {
+ premium: {
+ type: "object",
+ label: "Premium",
+ fields: {
+ enabled: {
+ type: "boolean",
+ label: "Enabled",
+ },
},
- apeKeySaltRounds: {
- type: "number",
- label: "Ape Key Salt Rounds",
- min: 5,
+ },
+ signUp: {
+ type: "boolean",
+ label: "Sign Up Enabled",
+ },
+ lastHashesCheck: {
+ type: "object",
+ label: "Last Hashes Check",
+ fields: {
+ enabled: { type: "boolean", label: "Enabled" },
+ maxHashes: { type: "number", label: "Hashes to store" },
},
},
- },
- users: {
- type: "object",
- label: "Users",
- fields: {
- premium: {
- type: "object",
- label: "Premium",
- fields: {
- enabled: {
- type: "boolean",
- label: "Enabled",
+ xp: {
+ type: "object",
+ label: "XP",
+ fields: {
+ enabled: {
+ type: "boolean",
+ label: "Enabled",
+ },
+ gainMultiplier: {
+ type: "number",
+ label: "Gain Multiplier",
+ },
+ funboxBonus: {
+ type: "number",
+ label: "Funbox Bonus",
+ },
+ maxDailyBonus: {
+ type: "number",
+ label: "Max Daily Bonus",
+ },
+ minDailyBonus: {
+ type: "number",
+ label: "Min Daily Bonus",
+ },
+ streak: {
+ type: "object",
+ label: "Streak",
+ fields: {
+ enabled: {
+ type: "boolean",
+ label: "Enabled",
+ },
+ maxStreakDays: {
+ type: "number",
+ label: "Max Streak Days",
+ },
+ maxStreakMultiplier: {
+ type: "number",
+ label: "Max Streak Multiplier",
+ },
},
},
},
- signUp: {
- type: "boolean",
- label: "Sign Up Enabled",
+ },
+ discordIntegration: {
+ type: "object",
+ label: "Discord Integration",
+ fields: {
+ enabled: {
+ type: "boolean",
+ label: "Enabled",
+ },
},
- lastHashesCheck: {
- type: "object",
- label: "Last Hashes Check",
- fields: {
- enabled: { type: "boolean", label: "Enabled" },
- maxHashes: { type: "number", label: "Hashes to store" },
+ },
+ autoBan: {
+ type: "object",
+ label: "Auto Ban",
+ fields: {
+ enabled: {
+ type: "boolean",
+ label: "Enabled",
+ },
+ maxCount: {
+ type: "number",
+ label: "Max Count",
+ min: 0,
+ },
+ maxHours: {
+ type: "number",
+ label: "Max Hours",
+ min: 0,
},
},
- xp: {
- type: "object",
- label: "XP",
- fields: {
- enabled: {
- type: "boolean",
- label: "Enabled",
- },
- gainMultiplier: {
- type: "number",
- label: "Gain Multiplier",
- },
- funboxBonus: {
- type: "number",
- label: "Funbox Bonus",
- },
- maxDailyBonus: {
- type: "number",
- label: "Max Daily Bonus",
- },
- minDailyBonus: {
- type: "number",
- label: "Min Daily Bonus",
- },
- streak: {
- type: "object",
- label: "Streak",
- fields: {
- enabled: {
- type: "boolean",
- label: "Enabled",
- },
- maxStreakDays: {
- type: "number",
- label: "Max Streak Days",
- },
- maxStreakMultiplier: {
- type: "number",
- label: "Max Streak Multiplier",
- },
- },
- },
+ },
+ inbox: {
+ type: "object",
+ label: "Inbox",
+ fields: {
+ enabled: {
+ type: "boolean",
+ label: "Enabled",
+ },
+ maxMail: {
+ type: "number",
+ label: "Max Messages",
+ min: 0,
},
},
- discordIntegration: {
- type: "object",
- label: "Discord Integration",
- fields: {
- enabled: {
- type: "boolean",
- label: "Enabled",
- },
+ },
+ profiles: {
+ type: "object",
+ label: "User Profiles",
+ fields: {
+ enabled: {
+ type: "boolean",
+ label: "Enabled",
},
},
- autoBan: {
- type: "object",
- label: "Auto Ban",
- fields: {
- enabled: {
- type: "boolean",
- label: "Enabled",
- },
- maxCount: {
- type: "number",
- label: "Max Count",
- min: 0,
- },
- maxHours: {
+ },
+ },
+ },
+ rateLimiting: {
+ type: "object",
+ label: "Rate Limiting",
+ fields: {
+ badAuthentication: {
+ type: "object",
+ label: "Bad Authentication Rate Limiter",
+ fields: {
+ enabled: {
+ type: "boolean",
+ label: "Enabled",
+ },
+ penalty: {
+ type: "number",
+ label: "Penalty",
+ min: 0,
+ },
+ flaggedStatusCodes: {
+ type: "array",
+ label: "Flagged Status Codes",
+ items: {
+ label: "Status Code",
type: "number",
- label: "Max Hours",
min: 0,
},
},
},
- inbox: {
+ },
+ },
+ },
+ dailyLeaderboards: {
+ type: "object",
+ label: "Daily Leaderboards",
+ fields: {
+ enabled: {
+ type: "boolean",
+ label: "Enabled",
+ },
+ maxResults: {
+ type: "number",
+ label: "Max Results",
+ min: 0,
+ },
+ leaderboardExpirationTimeInDays: {
+ type: "number",
+ label: "Leaderboard Expiration Time In Days",
+ min: 0,
+ },
+ validModeRules: {
+ type: "array",
+ label: "Valid Mode Rules",
+ items: {
type: "object",
- label: "Inbox",
+ label: "Rule",
fields: {
- enabled: {
- type: "boolean",
- label: "Enabled",
+ language: {
+ type: "string",
+ label: "Language",
},
- maxMail: {
- type: "number",
- label: "Max Messages",
- min: 0,
+ mode: {
+ type: "string",
+ label: "Mode",
+ },
+ mode2: {
+ type: "string",
+ label: "Secondary Mode",
},
},
},
- profiles: {
+ },
+ scheduleRewardsModeRules: {
+ type: "array",
+ label: "Schedule Rewards Mode Rules",
+ items: {
type: "object",
- label: "User Profiles",
+ label: "Rule",
fields: {
- enabled: {
- type: "boolean",
- label: "Enabled",
+ language: {
+ type: "string",
+ label: "Language",
+ },
+ mode: {
+ type: "string",
+ label: "Mode",
+ },
+ mode2: {
+ type: "string",
+ label: "Secondary Mode",
},
},
},
},
- },
- rateLimiting: {
- type: "object",
- label: "Rate Limiting",
- fields: {
- badAuthentication: {
+ topResultsToAnnounce: {
+ type: "number",
+ label: "Top Results To Announce",
+ min: 1,
+ hint: "This should atleast be 1. Setting to zero is very bad.",
+ },
+ xpRewardBrackets: {
+ type: "array",
+ label: "XP Reward Brackets",
+ items: {
type: "object",
- label: "Bad Authentication Rate Limiter",
+ label: "Bracket",
fields: {
- enabled: {
- type: "boolean",
- label: "Enabled",
+ minRank: {
+ type: "number",
+ label: "Min Rank",
+ min: 1,
+ },
+ maxRank: {
+ type: "number",
+ label: "Max Rank",
+ min: 1,
},
- penalty: {
+ minReward: {
type: "number",
- label: "Penalty",
+ label: "Min Reward",
min: 0,
},
- flaggedStatusCodes: {
- type: "array",
- label: "Flagged Status Codes",
- items: {
- label: "Status Code",
- type: "number",
- min: 0,
- },
+ maxReward: {
+ type: "number",
+ label: "Max Reward",
+ min: 0,
},
},
},
},
},
- dailyLeaderboards: {
- type: "object",
- label: "Daily Leaderboards",
- fields: {
- enabled: {
- type: "boolean",
- label: "Enabled",
- },
- maxResults: {
- type: "number",
- label: "Max Results",
- min: 0,
- },
- leaderboardExpirationTimeInDays: {
- type: "number",
- label: "Leaderboard Expiration Time In Days",
- min: 0,
- },
- validModeRules: {
- type: "array",
- label: "Valid Mode Rules",
- items: {
- type: "object",
- label: "Rule",
- fields: {
- language: {
- type: "string",
- label: "Language",
- },
- mode: {
- type: "string",
- label: "Mode",
- },
- mode2: {
- type: "string",
- label: "Secondary Mode",
- },
- },
+ },
+ leaderboards: {
+ type: "object",
+ label: "Leaderboards",
+ fields: {
+ weeklyXp: {
+ type: "object",
+ label: "Weekly XP",
+ fields: {
+ enabled: {
+ type: "boolean",
+ label: "Enabled",
},
- },
- scheduleRewardsModeRules: {
- type: "array",
- label: "Schedule Rewards Mode Rules",
- items: {
- type: "object",
- label: "Rule",
- fields: {
- language: {
- type: "string",
- label: "Language",
- },
- mode: {
- type: "string",
- label: "Mode",
- },
- mode2: {
- type: "string",
- label: "Secondary Mode",
- },
- },
+ expirationTimeInDays: {
+ type: "number",
+ label: "Expiration time in days",
+ min: 0,
+ hint: "This should atleast be 15, to allow for past week queries.",
},
- },
- topResultsToAnnounce: {
- type: "number",
- label: "Top Results To Announce",
- min: 1,
- hint: "This should atleast be 1. Setting to zero is very bad.",
- },
- xpRewardBrackets: {
- type: "array",
- label: "XP Reward Brackets",
- items: {
- type: "object",
- label: "Bracket",
- fields: {
- minRank: {
- type: "number",
- label: "Min Rank",
- min: 1,
- },
- maxRank: {
- type: "number",
- label: "Max Rank",
- min: 1,
- },
- minReward: {
- type: "number",
- label: "Min Reward",
- min: 0,
- },
- maxReward: {
- type: "number",
- label: "Max Reward",
- min: 0,
- },
- },
- },
- },
- },
- },
- leaderboards: {
- type: "object",
- label: "Leaderboards",
- fields: {
- weeklyXp: {
- type: "object",
- label: "Weekly XP",
- fields: {
- enabled: {
- type: "boolean",
- label: "Enabled",
- },
- expirationTimeInDays: {
- type: "number",
- label: "Expiration time in days",
- min: 0,
- hint: "This should atleast be 15, to allow for past week queries.",
- },
- xpRewardBrackets: {
- type: "array",
- label: "XP Reward Brackets",
- items: {
- type: "object",
- label: "Bracket",
- fields: {
- minRank: {
- type: "number",
- label: "Min Rank",
- min: 1,
- },
- maxRank: {
- type: "number",
- label: "Max Rank",
- min: 1,
- },
- minReward: {
- type: "number",
- label: "Min Reward",
- min: 0,
- },
- maxReward: {
- type: "number",
- label: "Max Reward",
- min: 0,
- },
+ xpRewardBrackets: {
+ type: "array",
+ label: "XP Reward Brackets",
+ items: {
+ type: "object",
+ label: "Bracket",
+ fields: {
+ minRank: {
+ type: "number",
+ label: "Min Rank",
+ min: 1,
+ },
+ maxRank: {
+ type: "number",
+ label: "Max Rank",
+ min: 1,
+ },
+ minReward: {
+ type: "number",
+ label: "Min Reward",
+ min: 0,
+ },
+ maxReward: {
+ type: "number",
+ label: "Max Reward",
+ min: 0,
},
},
},
@@ -596,4 +596,5 @@ export const CONFIGURATION_FORM_SCHEMA: ObjectSchema
},
},
},
- };
+ },
+};
diff --git a/backend/src/constants/funbox-list.ts b/backend/src/constants/funbox-list.ts
index 855095000258..e02c2a2f787a 100644
--- a/backend/src/constants/funbox-list.ts
+++ b/backend/src/constants/funbox-list.ts
@@ -306,6 +306,16 @@ const FunboxList: MonkeyTypes.FunboxMetadata[] = [
frontendFunctions: ["getWord"],
name: "binary",
},
+ {
+ canGetPb: false,
+ difficultyLevel: 1,
+ properties: ["ignoresLanguage", "ignoresLayout", "noLetters"],
+ frontendForcedConfig: {
+ numbers: [false],
+ },
+ frontendFunctions: ["getWord", "punctuateWord", "rememberSettings"],
+ name: "hexadecimal",
+ },
{
canGetPb: false,
difficultyLevel: 0,
diff --git a/backend/src/constants/profanities.ts b/backend/src/constants/profanities.ts
index e653327d1ba8..ef7330efb926 100644
--- a/backend/src/constants/profanities.ts
+++ b/backend/src/constants/profanities.ts
@@ -312,7 +312,6 @@ export const profanities = [
"feces",
"felcher",
"ficken",
- "fitt",
"flikker",
"foreskin",
"fotze",
diff --git a/backend/src/dal/admin-uids.ts b/backend/src/dal/admin-uids.ts
index cd2bbfaf58c7..8e5029f0d82e 100644
--- a/backend/src/dal/admin-uids.ts
+++ b/backend/src/dal/admin-uids.ts
@@ -1,7 +1,11 @@
+import { Collection, WithId } from "mongodb";
import * as db from "../init/db";
+export const getCollection = (): Collection> =>
+ db.collection("admin-uids");
+
export async function isAdmin(uid: string): Promise {
- const doc = await db.collection("admin-uids").findOne({ uid });
+ const doc = await getCollection().findOne({ uid });
if (doc) {
return true;
} else {
diff --git a/backend/src/dal/ape-keys.ts b/backend/src/dal/ape-keys.ts
index 46a08aaedbac..16a03a60ab62 100644
--- a/backend/src/dal/ape-keys.ts
+++ b/backend/src/dal/ape-keys.ts
@@ -1,11 +1,11 @@
import _ from "lodash";
import * as db from "../init/db";
import {
- Filter,
+ type Filter,
+ type MatchKeysAndValues,
+ type WithId,
ObjectId,
- MatchKeysAndValues,
Collection,
- WithId,
} from "mongodb";
import MonkeyError from "../utils/error";
@@ -34,8 +34,7 @@ export async function getApeKey(
}
export async function countApeKeysForUser(uid: string): Promise {
- const apeKeys = await getApeKeys(uid);
- return _.size(apeKeys);
+ return getApeKeysCollection().countDocuments({ uid });
}
export async function addApeKey(apeKey: MonkeyTypes.ApeKeyDB): Promise {
@@ -64,9 +63,11 @@ async function updateApeKey(
export async function editApeKey(
uid: string,
keyId: string,
- name: string,
- enabled: boolean
+ name?: string,
+ enabled?: boolean
): Promise {
+ //check if there is a change
+ if (name === undefined && enabled === undefined) return;
const apeKeyUpdates = {
name,
enabled,
diff --git a/backend/src/dal/blocklist.ts b/backend/src/dal/blocklist.ts
index 78e53daef2d9..1c94bcf45146 100644
--- a/backend/src/dal/blocklist.ts
+++ b/backend/src/dal/blocklist.ts
@@ -1,11 +1,9 @@
import { Collection } from "mongodb";
import * as db from "../init/db";
import { createHash } from "crypto";
+import { User } from "@monkeytype/shared-types";
-type BlocklistEntryProperties = Pick<
- SharedTypes.User,
- "name" | "email" | "discordId"
->;
+type BlocklistEntryProperties = Pick;
// Export for use in tests
export const getCollection = (): Collection =>
db.collection("blocklist");
diff --git a/backend/src/dal/config.ts b/backend/src/dal/config.ts
index ac7fb1ee776d..67cd2abc4268 100644
--- a/backend/src/dal/config.ts
+++ b/backend/src/dal/config.ts
@@ -1,6 +1,7 @@
-import { UpdateResult } from "mongodb";
+import { Collection, UpdateResult } from "mongodb";
import * as db from "../init/db";
import _ from "lodash";
+import { Config, PartialConfig } from "@monkeytype/contracts/schemas/configs";
const configLegacyProperties = [
"swapEscAndTab",
@@ -22,9 +23,19 @@ const configLegacyProperties = [
"enableAds",
];
+type DBConfig = {
+ _id: ObjectId;
+ uid: string;
+ config: PartialConfig;
+};
+
+// Export for use in tests
+export const getConfigCollection = (): Collection =>
+ db.collection("configs");
+
export async function saveConfig(
uid: string,
- config: SharedTypes.Config
+ config: Partial
): Promise {
const configChanges = _.mapKeys(config, (_value, key) => `config.${key}`);
@@ -32,24 +43,18 @@ export async function saveConfig(
_.map(configLegacyProperties, (key) => [`config.${key}`, ""])
) as Record;
- return await db
- .collection("configs")
- .updateOne(
- { uid },
- { $set: configChanges, $unset: unset },
- { upsert: true }
- );
+ return await getConfigCollection().updateOne(
+ { uid },
+ { $set: configChanges, $unset: unset },
+ { upsert: true }
+ );
}
-export async function getConfig(
- uid: string
-): Promise {
- const config = await db
- .collection("configs")
- .findOne({ uid });
+export async function getConfig(uid: string): Promise {
+ const config = await getConfigCollection().findOne({ uid });
return config;
}
export async function deleteConfig(uid: string): Promise {
- await db.collection("configs").deleteOne({ uid });
+ await getConfigCollection().deleteOne({ uid });
}
diff --git a/backend/src/dal/leaderboards.ts b/backend/src/dal/leaderboards.ts
index f30771034d7e..7868b4031aee 100644
--- a/backend/src/dal/leaderboards.ts
+++ b/backend/src/dal/leaderboards.ts
@@ -5,22 +5,40 @@ import { setLeaderboard } from "../utils/prometheus";
import { isDevEnvironment } from "../utils/misc";
import { getCachedConfiguration } from "../init/configuration";
+import { addLog } from "./logs";
+import { Collection } from "mongodb";
+import {
+ LeaderboardEntry,
+ LeaderboardRank,
+} from "@monkeytype/contracts/schemas/leaderboards";
+import { omit } from "lodash";
+
+export type DBLeaderboardEntry = LeaderboardEntry & {
+ _id: ObjectId;
+};
+
+export const getCollection = (key: {
+ language: string;
+ mode: string;
+ mode2: string;
+}): Collection =>
+ db.collection(
+ `leaderboards.${key.language}.${key.mode}.${key.mode2}`
+ );
+
export async function get(
mode: string,
mode2: string,
language: string,
skip: number,
limit = 50
-): Promise {
+): Promise {
//if (leaderboardUpdating[`${language}_${mode}_${mode2}`]) return false;
if (limit > 50 || limit <= 0) limit = 50;
if (skip < 0) skip = 0;
try {
- const preset = await db
- .collection(
- `leaderboards.${language}.${mode}.${mode2}`
- )
+ const preset = await getCollection({ language, mode, mode2 })
.find()
.sort({ rank: 1 })
.skip(skip)
@@ -31,8 +49,9 @@ export async function get(
.premium.enabled;
if (!premiumFeaturesEnabled) {
- preset.forEach((it) => (it.isPremium = undefined));
+ return preset.map((it) => omit(it, "isPremium"));
}
+
return preset;
} catch (e) {
if (e.error === 175) {
@@ -43,32 +62,26 @@ export async function get(
}
}
-type GetRankResponse = {
- count: number;
- rank: number | null;
- entry: SharedTypes.LeaderboardEntry | null;
-};
-
export async function getRank(
mode: string,
mode2: string,
language: string,
uid: string
-): Promise {
+): Promise {
try {
- const entry = await db
- .collection(
- `leaderboards.${language}.${mode}.${mode2}`
- )
- .findOne({ uid });
- const count = await db
- .collection(`leaderboards.${language}.${mode}.${mode2}`)
- .estimatedDocumentCount();
+ const entry = await getCollection({ language, mode, mode2 }).findOne({
+ uid,
+ });
+ const count = await getCollection({
+ language,
+ mode,
+ mode2,
+ }).estimatedDocumentCount();
return {
count,
- rank: entry ? entry.rank : null,
- entry,
+ rank: entry?.rank,
+ entry: entry !== null ? entry : undefined,
};
} catch (e) {
if (e.error === 175) {
@@ -91,7 +104,7 @@ export async function update(
const lbCollectionName = `leaderboards.${language}.${mode}.${mode2}`;
const lb = db
.collection("users")
- .aggregate(
+ .aggregate(
[
{
$match: {
@@ -238,7 +251,7 @@ export async function update(
const timeToRunIndex = (end2 - start2) / 1000;
const timeToSaveHistogram = (end3 - start3) / 1000; // not sent to prometheus yet
- void Logger.logToDb(
+ void addLog(
`system_lb_update_${language}_${mode}_${mode2}`,
`Aggregate ${timeToRunAggregate}s, loop 0s, insert 0s, index ${timeToRunIndex}s, histogram ${timeToSaveHistogram}`
);
diff --git a/backend/src/dal/logs.ts b/backend/src/dal/logs.ts
new file mode 100644
index 000000000000..052701cf2f4b
--- /dev/null
+++ b/backend/src/dal/logs.ts
@@ -0,0 +1,58 @@
+import { Collection, ObjectId } from "mongodb";
+import * as db from "../init/db";
+import Logger from "../utils/logger";
+
+type DbLog = {
+ _id: ObjectId;
+ type?: string;
+ timestamp: number;
+ uid: string;
+ important?: boolean;
+ event: string;
+ message: string | Record;
+};
+
+export const getLogsCollection = (): Collection =>
+ db.collection("logs");
+
+async function insertIntoDb(
+ event: string,
+ message: string | Record,
+ uid = "",
+ important = false
+): Promise {
+ const dbLog: DbLog = {
+ _id: new ObjectId(),
+ timestamp: Date.now(),
+ uid: uid ?? "",
+ event: event,
+ message: message,
+ important: important,
+ };
+
+ if (!important) delete dbLog.important;
+
+ Logger.info(`${event}\t${uid}\t${JSON.stringify(message)}`);
+
+ await getLogsCollection().insertOne(dbLog);
+}
+
+export async function addLog(
+ event: string,
+ message: string | Record,
+ uid = ""
+): Promise {
+ await insertIntoDb(event, message, uid);
+}
+
+export async function addImportantLog(
+ event: string,
+ message: string | Record,
+ uid = ""
+): Promise {
+ await insertIntoDb(event, message, uid, true);
+}
+
+export async function deleteUserLogs(uid: string): Promise {
+ await getLogsCollection().deleteMany({ uid });
+}
diff --git a/backend/src/dal/preset.ts b/backend/src/dal/preset.ts
index a38bd50120ba..4186a1289a0f 100644
--- a/backend/src/dal/preset.ts
+++ b/backend/src/dal/preset.ts
@@ -1,10 +1,15 @@
import MonkeyError from "../utils/error";
import * as db from "../init/db";
-import { ObjectId, Filter, Collection, WithId } from "mongodb";
+import { ObjectId, type Filter, Collection, type WithId } from "mongodb";
+import { Preset } from "@monkeytype/contracts/schemas/presets";
const MAX_PRESETS = 10;
-type DBConfigPreset = MonkeyTypes.WithObjectId;
+type DBConfigPreset = MonkeyTypes.WithObjectId<
+ Preset & {
+ uid: string;
+ }
+>;
function getPresetKeyFilter(
uid: string,
@@ -33,36 +38,32 @@ export async function getPresets(uid: string): Promise {
export async function addPreset(
uid: string,
- name: string,
- config: SharedTypes.ConfigPreset
+ preset: Omit
): Promise {
- const presets = await getPresets(uid);
- if (presets.length >= MAX_PRESETS) {
+ const presets = await getPresetsCollection().countDocuments({ uid });
+
+ if (presets >= MAX_PRESETS) {
throw new MonkeyError(409, "Too many presets");
}
- const preset = await getPresetsCollection().insertOne({
+ const result = await getPresetsCollection().insertOne({
+ ...preset,
_id: new ObjectId(),
uid,
- name,
- config,
});
return {
- presetId: preset.insertedId.toHexString(),
+ presetId: result.insertedId.toHexString(),
};
}
-export async function editPreset(
- uid: string,
- presetId: string,
- name: string,
- config: SharedTypes.ConfigPreset | null | undefined
-): Promise {
+export async function editPreset(uid: string, preset: Preset): Promise {
+ const config = preset.config;
const presetUpdates =
config !== undefined && config !== null && Object.keys(config).length > 0
- ? { name, config }
- : { name };
- await getPresetsCollection().updateOne(getPresetKeyFilter(uid, presetId), {
+ ? { name: preset.name, config }
+ : { name: preset.name };
+
+ await getPresetsCollection().updateOne(getPresetKeyFilter(uid, preset._id), {
$set: presetUpdates,
});
}
diff --git a/backend/src/dal/psa.ts b/backend/src/dal/psa.ts
index 3f7ace3b7766..904c30b8a09a 100644
--- a/backend/src/dal/psa.ts
+++ b/backend/src/dal/psa.ts
@@ -1,7 +1,8 @@
+import { PSA } from "@monkeytype/contracts/schemas/psas";
import * as db from "../init/db";
-type PSA = MonkeyTypes.WithObjectId;
+export type DBPSA = MonkeyTypes.WithObjectId;
-export async function get(): Promise {
- return await db.collection("psa").find().toArray();
+export async function get(): Promise {
+ return await db.collection("psa").find().toArray();
}
diff --git a/backend/src/dal/public.ts b/backend/src/dal/public.ts
index 5927da57ee9a..50a230a7867c 100644
--- a/backend/src/dal/public.ts
+++ b/backend/src/dal/public.ts
@@ -1,12 +1,16 @@
import * as db from "../init/db";
import { roundTo2 } from "../utils/misc";
import MonkeyError from "../utils/error";
+import {
+ TypingStats,
+ SpeedHistogram,
+} from "@monkeytype/contracts/schemas/public";
-type PublicTypingStatsDB = SharedTypes.PublicTypingStats & { _id: "stats" };
-type PublicSpeedStatsDB = {
+export type PublicTypingStatsDB = TypingStats & { _id: "stats" };
+export type PublicSpeedStatsDB = {
_id: "speedStatsHistogram";
- english_time_15: SharedTypes.SpeedHistogram;
- english_time_60: SharedTypes.SpeedHistogram;
+ english_time_15: SpeedHistogram;
+ english_time_60: SpeedHistogram;
};
export async function updateStats(
@@ -34,12 +38,21 @@ export async function getSpeedHistogram(
language: string,
mode: string,
mode2: string
-): Promise> {
- const key = `${language}_${mode}_${mode2}`;
+): Promise {
+ const key = `${language}_${mode}_${mode2}` as keyof PublicSpeedStatsDB;
+
+ if (key === "_id") {
+ throw new MonkeyError(
+ 400,
+ "Invalid speed histogram key",
+ "get speed histogram"
+ );
+ }
const stats = await db
.collection("public")
.findOne({ _id: "speedStatsHistogram" }, { projection: { [key]: 1 } });
+
return stats?.[key] ?? {};
}
diff --git a/backend/src/dal/result.ts b/backend/src/dal/result.ts
index 5e19576bbf5d..57946bf7ec7d 100644
--- a/backend/src/dal/result.ts
+++ b/backend/src/dal/result.ts
@@ -1,16 +1,17 @@
import _ from "lodash";
-import { Collection, DeleteResult, ObjectId, UpdateResult } from "mongodb";
+import {
+ Collection,
+ type DeleteResult,
+ ObjectId,
+ type UpdateResult,
+} from "mongodb";
import MonkeyError from "../utils/error";
import * as db from "../init/db";
import { getUser, getTags } from "./user";
-type DBResult = MonkeyTypes.WithObjectId<
- SharedTypes.DBResult
->;
-
-export const getResultCollection = (): Collection =>
- db.collection("results");
+export const getResultCollection = (): Collection =>
+ db.collection("results");
export async function addResult(
uid: string,
@@ -74,14 +75,14 @@ export async function getResult(
export async function getLastResult(
uid: string
-): Promise> {
+): Promise {
const [lastResult] = await getResultCollection()
.find({ uid })
.sort({ timestamp: -1 })
.limit(1)
.toArray();
if (!lastResult) throw new MonkeyError(404, "No results found");
- return _.omit(lastResult, "uid");
+ return lastResult;
}
export async function getResultByTimestamp(
diff --git a/backend/src/dal/user.ts b/backend/src/dal/user.ts
index 81f3e553ff5f..da65ebaa3ea2 100644
--- a/backend/src/dal/user.ts
+++ b/backend/src/dal/user.ts
@@ -3,19 +3,40 @@ import { containsProfanity, isUsernameValid } from "../utils/validation";
import { canFunboxGetPb, checkAndUpdatePb } from "../utils/pb";
import * as db from "../init/db";
import MonkeyError from "../utils/error";
-import { Collection, ObjectId, Long, UpdateFilter, Filter } from "mongodb";
-import Logger from "../utils/logger";
+import {
+ Collection,
+ ObjectId,
+ Long,
+ type UpdateFilter,
+ type Filter,
+} from "mongodb";
import { flattenObjectDeep, isToday, isYesterday } from "../utils/misc";
import { getCachedConfiguration } from "../init/configuration";
import { getDayOfYear } from "date-fns";
import { UTCDate } from "@date-fns/utc";
+import {
+ AllRewards,
+ Badge,
+ CustomTheme,
+ MonkeyMail,
+ UserInventory,
+ UserProfileDetails,
+ UserQuoteRatings,
+ UserStreak,
+} from "@monkeytype/shared-types";
+import {
+ Mode,
+ Mode2,
+ PersonalBest,
+} from "@monkeytype/contracts/schemas/shared";
+import { addImportantLog } from "./logs";
+import { ResultFilters } from "@monkeytype/contracts/schemas/users";
+import { Result as ResultType } from "@monkeytype/contracts/schemas/results";
+import { Configuration } from "@monkeytype/contracts/schemas/configuration";
const SECONDS_PER_HOUR = 3600;
-type Result = Omit<
- SharedTypes.DBResult,
- "_id" | "name"
->;
+type Result = Omit, "_id" | "name">;
// Export for use in tests
export const getUsersCollection = (): Collection =>
@@ -177,7 +198,7 @@ export async function optOutOfLeaderboards(uid: string): Promise {
export async function updateQuoteRatings(
uid: string,
- quoteRatings: SharedTypes.UserQuoteRatings
+ quoteRatings: UserQuoteRatings
): Promise {
await updateUser(
{ uid },
@@ -268,7 +289,7 @@ export async function isDiscordIdAvailable(
export async function addResultFilterPreset(
uid: string,
- resultFilter: SharedTypes.ResultFilters,
+ resultFilter: ResultFilters,
maxFiltersPerUser: number
): Promise {
if (maxFiltersPerUser === 0) {
@@ -394,8 +415,8 @@ export async function removeTagPb(uid: string, _id: string): Promise {
export async function updateLbMemory(
uid: string,
- mode: SharedTypes.Config.Mode,
- mode2: SharedTypes.Config.Mode2,
+ mode: Mode,
+ mode2: Mode2,
language: string,
rank: number
): Promise {
@@ -644,7 +665,7 @@ export async function incrementTestActivity(
export async function addTheme(
uid: string,
- { name, colors }: Omit
+ { name, colors }: Omit
): Promise<{ _id: ObjectId; name: string }> {
const _id = new ObjectId();
@@ -688,7 +709,7 @@ export async function removeTheme(uid: string, id: string): Promise {
export async function editTheme(
uid: string,
id: string,
- { name, colors }: Omit
+ { name, colors }: Omit
): Promise {
const themeId = new ObjectId(id);
@@ -715,16 +736,16 @@ export async function getPersonalBests(
uid: string,
mode: string,
mode2?: string
-): Promise {
+): Promise {
const user = await getPartialUser(uid, "get personal bests", [
"personalBests",
]);
if (mode2 !== undefined) {
- return user.personalBests?.[mode]?.[mode2];
+ return user.personalBests?.[mode]?.[mode2] as PersonalBest;
}
- return user.personalBests?.[mode];
+ return user.personalBests?.[mode] as PersonalBest;
}
export async function getStats(
@@ -838,7 +859,7 @@ export async function recordAutoBanEvent(
}
await getUsersCollection().updateOne({ uid }, { $set: updateObj });
- void Logger.logToDb(
+ void addImportantLog(
"user_auto_banned",
{ autoBanTimestamps, banningUser },
uid
@@ -848,8 +869,8 @@ export async function recordAutoBanEvent(
export async function updateProfile(
uid: string,
- profileDetailUpdates: Partial,
- inventory?: SharedTypes.UserInventory
+ profileDetailUpdates: Partial,
+ inventory?: UserInventory
): Promise {
const profileUpdates = _.omitBy(
flattenObjectDeep(profileDetailUpdates, "profileDetails"),
@@ -882,12 +903,12 @@ export async function getInbox(
type AddToInboxBulkEntry = {
uid: string;
- mail: SharedTypes.MonkeyMail[];
+ mail: MonkeyMail[];
};
export async function addToInboxBulk(
entries: AddToInboxBulkEntry[],
- inboxConfig: SharedTypes.Configuration["users"]["inbox"]
+ inboxConfig: Configuration["users"]["inbox"]
): Promise {
const { enabled, maxMail } = inboxConfig;
@@ -914,8 +935,8 @@ export async function addToInboxBulk(
export async function addToInbox(
uid: string,
- mail: SharedTypes.MonkeyMail[],
- inboxConfig: SharedTypes.Configuration["users"]["inbox"]
+ mail: MonkeyMail[],
+ inboxConfig: Configuration["users"]["inbox"]
): Promise {
const { enabled, maxMail } = inboxConfig;
@@ -949,7 +970,7 @@ export async function updateInbox(
//we don't need to read mails that are going to be deleted because
//Rewards will be claimed on unread mails on deletion
const readSet = [...new Set(mailToRead)].filter(
- (it) => deleteSet.includes(it) === false
+ (it) => !deleteSet.includes(it)
);
const update = await getUsersCollection().updateOne({ uid }, [
@@ -960,9 +981,9 @@ export async function updateInbox(
lang: "js",
args: ["$inbox", "$xp", "$inventory", deleteSet, readSet],
body: function (
- inbox: SharedTypes.MonkeyMail[],
+ inbox: MonkeyMail[],
xp: number,
- inventory: SharedTypes.UserInventory,
+ inventory: UserInventory,
deletedIds: string[],
readIds: string[]
): Pick {
@@ -971,27 +992,24 @@ export async function updateInbox(
);
const toBeRead = inbox.filter(
- (it) => readIds.includes(it.id) && it.read === false
+ (it) => readIds.includes(it.id) && !it.read
);
//flatMap rewards
- const rewards: SharedTypes.AllRewards[] = [
- ...toBeRead,
- ...toBeDeleted,
- ]
- .filter((it) => it.read === false)
+ const rewards: AllRewards[] = [...toBeRead, ...toBeDeleted]
+ .filter((it) => !it.read)
.reduce((arr, current) => {
return [...arr, ...current.rewards];
}, []);
const xpGain = rewards
.filter((it) => it.type === "xp")
- .map((it) => it.item as number)
+ .map((it) => it.item)
.reduce((s, a) => s + a, 0);
const badgesToClaim = rewards
.filter((it) => it.type === "badge")
- .map((it) => it.item as SharedTypes.Badge);
+ .map((it) => it.item);
if (inventory === null)
inventory = {
@@ -1000,7 +1018,7 @@ export async function updateInbox(
if (inventory.badges === null) inventory.badges = [];
const uniqueBadgeIds = new Set();
- const newBadges: SharedTypes.Badge[] = [];
+ const newBadges: Badge[] = [];
for (const badge of [...inventory.badges, ...badgesToClaim]) {
if (uniqueBadgeIds.has(badge.id)) continue;
@@ -1049,7 +1067,7 @@ export async function updateStreak(
timestamp: number
): Promise {
const user = await getPartialUser(uid, "calculate streak", ["streak"]);
- const streak: SharedTypes.UserStreak = {
+ const streak: UserStreak = {
lastResultTimestamp: user.streak?.lastResultTimestamp ?? 0,
length: user.streak?.length ?? 0,
maxLength: user.streak?.maxLength ?? 0,
@@ -1059,7 +1077,11 @@ export async function updateStreak(
if (isYesterday(streak.lastResultTimestamp, streak.hourOffset ?? 0)) {
streak.length += 1;
} else if (!isToday(streak.lastResultTimestamp, streak.hourOffset ?? 0)) {
- void Logger.logToDb("streak_lost", JSON.parse(JSON.stringify(streak)), uid);
+ void addImportantLog(
+ "streak_lost",
+ JSON.parse(JSON.stringify(streak)),
+ uid
+ );
streak.length = 1;
}
@@ -1108,7 +1130,7 @@ export async function checkIfUserIsPremium(
): Promise {
const premiumFeaturesEnabled = (await getCachedConfiguration(true)).users
.premium.enabled;
- if (premiumFeaturesEnabled !== true) {
+ if (!premiumFeaturesEnabled) {
return false;
}
const user =
diff --git a/backend/src/documentation/internal-swagger.json b/backend/src/documentation/internal-swagger.json
index 9e293508eec9..be8f5d030664 100644
--- a/backend/src/documentation/internal-swagger.json
+++ b/backend/src/documentation/internal-swagger.json
@@ -1,7 +1,7 @@
{
"swagger": "2.0",
"info": {
- "description": "These are the set of `internal` endpoints dedicated to the Monkeytype web client. Authentication for these endpoints requires a user account.",
+ "description": "These are the set of `internal` endpoints dedicated to the Monkeytype web client. Authentication for these endpoints requires a user account.\nNote: We are currently re-working our APIs. Some endpoints are documented at https://api.monkeytype.com/docs/v2/internal",
"version": "1.0.0",
"title": "Monkeytype",
"termsOfService": "https://monkeytype.com/terms-of-service",
@@ -23,30 +23,6 @@
"name": "users",
"description": "User data and related operations"
},
- {
- "name": "psas",
- "description": "Public service announcements"
- },
- {
- "name": "presets",
- "description": "Preset data and related operations"
- },
- {
- "name": "configs",
- "description": "User configuration data and related operations"
- },
- {
- "name": "ape-keys",
- "description": "ApeKey data and related operations"
- },
- {
- "name": "leaderboards",
- "description": "Leaderboard data"
- },
- {
- "name": "results",
- "description": "Result data and related operations"
- },
{
"name": "quotes",
"description": "Quote data and related operations"
@@ -428,406 +404,6 @@
}
}
},
- "/psas": {
- "get": {
- "tags": ["psas"],
- "summary": "Gets the latest public service announcements",
- "responses": {
- "default": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Response"
- }
- }
- }
- }
- },
- "/presets": {
- "get": {
- "tags": ["presets"],
- "summary": "Gets saved preset configurations",
- "responses": {
- "default": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Response"
- }
- }
- }
- },
- "post": {
- "tags": ["presets"],
- "summary": "Creates a preset configuration",
- "parameters": [
- {
- "in": "body",
- "name": "body",
- "required": true,
- "schema": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string"
- },
- "config": {
- "type": "object"
- }
- }
- }
- }
- ],
- "responses": {
- "default": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Response"
- }
- }
- }
- },
- "patch": {
- "tags": ["presets"],
- "summary": "Updates an existing preset configuration",
- "parameters": [
- {
- "in": "body",
- "name": "body",
- "required": true,
- "schema": {
- "type": "object",
- "properties": {
- "_id": {
- "type": "string"
- },
- "name": {
- "type": "string"
- },
- "config": {
- "type": "object"
- }
- }
- }
- }
- ],
- "responses": {
- "default": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Response"
- }
- }
- }
- }
- },
- "/presets/{presetId}": {
- "delete": {
- "tags": ["presets"],
- "summary": "Deletes a preset configuration",
- "parameters": [
- {
- "in": "path",
- "name": "presetId",
- "required": true,
- "type": "string"
- }
- ],
- "responses": {
- "default": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Response"
- }
- }
- }
- }
- },
- "/configs": {
- "get": {
- "tags": ["configs"],
- "summary": "Gets the user's current configuration",
- "responses": {
- "default": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Response"
- }
- }
- }
- },
- "patch": {
- "tags": ["configs"],
- "summary": "Updates a user's configuration",
- "parameters": [
- {
- "in": "body",
- "name": "body",
- "required": true,
- "schema": {
- "type": "object",
- "properties": {
- "config": {
- "type": "object"
- }
- }
- }
- }
- ],
- "responses": {
- "default": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Response"
- }
- }
- }
- }
- },
- "/ape-keys": {
- "get": {
- "tags": ["ape-keys"],
- "summary": "Gets ApeKeys created by a user",
- "responses": {
- "default": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Response"
- }
- }
- }
- },
- "post": {
- "tags": ["ape-keys"],
- "summary": "Creates an ApeKey",
- "parameters": [
- {
- "in": "body",
- "name": "body",
- "required": true,
- "schema": {
- "type": "object",
- "properties": {
- "name": {
- "type": "string"
- },
- "enabled": {
- "type": "boolean"
- }
- }
- }
- }
- ],
- "responses": {
- "default": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Response"
- }
- }
- }
- }
- },
- "/ape-keys/{apeKeyId}": {
- "patch": {
- "tags": ["ape-keys"],
- "summary": "Updates an ApeKey",
- "parameters": [
- {
- "in": "path",
- "name": "apeKeyId",
- "required": true,
- "type": "string"
- }
- ],
- "responses": {
- "default": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Response"
- }
- }
- }
- },
- "delete": {
- "tags": ["ape-keys"],
- "summary": "Deletes an ApeKey",
- "parameters": [
- {
- "in": "path",
- "name": "apeKeyId",
- "required": true,
- "type": "string"
- }
- ],
- "responses": {
- "default": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Response"
- }
- }
- }
- }
- },
- "/leaderboards": {
- "get": {
- "tags": ["leaderboards"],
- "summary": "Gets a leaderboard",
- "parameters": [
- {
- "in": "query",
- "name": "language",
- "type": "string"
- },
- {
- "in": "query",
- "name": "mode",
- "type": "string"
- },
- {
- "in": "query",
- "name": "mode2",
- "type": "string"
- },
- {
- "in": "query",
- "name": "skip",
- "type": "number"
- },
- {
- "in": "query",
- "name": "limit",
- "type": "number"
- }
- ],
- "responses": {
- "default": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Response"
- }
- }
- }
- }
- },
- "/leaderboards/rank": {
- "get": {
- "tags": ["leaderboards"],
- "summary": "Gets a user's rank from a leaderboard",
- "parameters": [
- {
- "in": "query",
- "name": "language",
- "type": "string"
- },
- {
- "in": "query",
- "name": "mode",
- "type": "string"
- },
- {
- "in": "query",
- "name": "mode2",
- "type": "string"
- }
- ],
- "responses": {
- "default": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Response"
- }
- }
- }
- }
- },
- "/results": {
- "get": {
- "tags": ["results"],
- "summary": "Gets a history of a user's results",
- "responses": {
- "default": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Response"
- }
- }
- }
- },
- "post": {
- "tags": ["results"],
- "summary": "Save a user's result",
- "parameters": [
- {
- "in": "body",
- "name": "body",
- "required": true,
- "schema": {
- "type": "object",
- "properties": {
- "result": {
- "type": "object"
- }
- }
- }
- }
- ],
- "responses": {
- "default": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Response"
- }
- }
- }
- },
- "delete": {
- "tags": ["results"],
- "summary": "Deletes all results",
- "responses": {
- "default": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Response"
- }
- }
- }
- }
- },
- "/results/tags": {
- "patch": {
- "tags": ["results"],
- "summary": "Labels a result with the specified tags",
- "parameters": [
- {
- "in": "body",
- "name": "body",
- "required": true,
- "schema": {
- "type": "object",
- "properties": {
- "tagIds": {
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "resultId": {
- "type": "string"
- }
- }
- }
- }
- ],
- "responses": {
- "default": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Response"
- }
- }
- }
- }
- },
"/quotes": {
"get": {
"tags": ["quotes"],
diff --git a/backend/src/documentation/public-swagger.json b/backend/src/documentation/public-swagger.json
index aadc66ea5f81..30c9d8a0faf7 100644
--- a/backend/src/documentation/public-swagger.json
+++ b/backend/src/documentation/public-swagger.json
@@ -1,7 +1,7 @@
{
"swagger": "2.0",
"info": {
- "description": "Documentation for the public endpoints provided by the Monkeytype API server.\n\nNote that authentication is performed with the Authorization HTTP header in the format `Authorization: ApeKey YOUR_APE_KEY`\n\nThere is a rate limit of `30 requests per minute` across all endpoints with some endpoints being more strict. Rate limit rates are shared across all ape keys.",
+ "description": "Documentation for the public endpoints provided by the Monkeytype API server.\n\nNote that authentication is performed with the Authorization HTTP header in the format `Authorization: ApeKey YOUR_APE_KEY`\n\nThere is a rate limit of `30 requests per minute` across all endpoints with some endpoints being more strict. Rate limit rates are shared across all ape keys.\n\nNote: We are currently re-working our APIs. Some endpoints are documented at https://api.monkeytype.com/docs/v2/public",
"version": "1.0.0",
"title": "Monkeytype API",
"termsOfService": "https://monkeytype.com/terms-of-service",
@@ -19,14 +19,6 @@
{
"name": "users",
"description": "User data and related operations"
- },
- {
- "name": "leaderboards",
- "description": "Leaderboard data and related operations"
- },
- {
- "name": "results",
- "description": "User results data and related operations"
}
],
"paths": {
@@ -147,151 +139,6 @@
}
}
}
- },
- "/results": {
- "get": {
- "tags": ["results"],
- "summary": "Gets up to 1000 results (endpoint limited to 30 requests per day)",
- "parameters": [
- {
- "name": "onOrAfterTimestamp",
- "in": "query",
- "description": "Get results on or after a Unix timestamp in milliseconds. Must be no earlier than Thu May 14 2020 04:00:00 GMT+0000 (i.e., 1670454228000). If omitted, defaults to the most recent results.",
- "required": false,
- "type": "number"
- },
- {
- "name": "limit",
- "in": "query",
- "description": "The maximum number of items to return per page.",
- "required": false,
- "type": "number",
- "minimum": 0,
- "maximum": 1000
- },
- {
- "name": "offset",
- "in": "query",
- "description": "The offset of the item at which to begin the response.",
- "required": false,
- "type": "number",
- "minimum": 0
- }
- ],
- "responses": {
- "200": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Results"
- }
- }
- }
- }
- },
- "/results/last": {
- "get": {
- "tags": ["results"],
- "summary": "Gets a user's last saved result",
- "responses": {
- "200": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Result"
- }
- }
- }
- }
- },
- "/leaderboards": {
- "get": {
- "tags": ["leaderboards"],
- "summary": "Gets global leaderboard data",
- "parameters": [
- {
- "name": "language",
- "in": "query",
- "description": "The leaderboard's language (i.e., english)",
- "required": true,
- "type": "string"
- },
- {
- "name": "mode",
- "in": "query",
- "description": "The primary mode (i.e., time)",
- "required": true,
- "type": "string"
- },
- {
- "name": "mode2",
- "in": "query",
- "description": "The secondary mode (i.e., 60)",
- "required": true,
- "type": "string"
- },
- {
- "name": "skip",
- "in": "query",
- "description": "How many leaderboard entries to skip",
- "required": false,
- "type": "number",
- "minimum": 0
- },
- {
- "name": "limit",
- "in": "query",
- "description": "How many leaderboard entries to request",
- "required": false,
- "type": "number",
- "minimum": 0,
- "maximum": 50
- }
- ],
- "responses": {
- "200": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/LeaderboardEntry"
- }
- }
- }
- }
- },
- "/leaderboards/rank": {
- "get": {
- "tags": ["leaderboards"],
- "summary": "Gets your qualifying rank from a leaderboard",
- "parameters": [
- {
- "name": "language",
- "in": "query",
- "description": "The leaderboard's language (i.e., english)",
- "required": true,
- "type": "string"
- },
- {
- "name": "mode",
- "in": "query",
- "description": "The primary mode (i.e., time)",
- "required": true,
- "type": "string"
- },
- {
- "name": "mode2",
- "in": "query",
- "description": "The secondary mode (i.e., 60)",
- "required": true,
- "type": "string"
- }
- ],
- "responses": {
- "200": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/LeaderboardEntry"
- }
- }
- }
- }
}
},
"definitions": {
@@ -606,215 +453,6 @@
}
}
},
- "LeaderboardEntry": {
- "type": "object",
- "properties": {
- "uid": {
- "type": "string",
- "example": "6226b17aebc27a4a8d1ce04b"
- },
- "acc": {
- "type": "number",
- "format": "double",
- "example": 97.96
- },
- "consistency": {
- "type": "number",
- "format": "double",
- "example": 83.29
- },
- "lazyMode": {
- "type": "boolean",
- "example": false
- },
- "name": {
- "type": "string",
- "example": "Miodec"
- },
- "punctuation": {
- "type": "boolean",
- "example": false
- },
- "rank": {
- "type": "integer",
- "example": 3506
- },
- "raw": {
- "type": "number",
- "format": "double",
- "example": 145.18
- },
- "wpm": {
- "type": "number",
- "format": "double",
- "example": 141.18
- },
- "timestamp": {
- "type": "integer",
- "example": 1644438189583
- },
- "discordId": {
- "type": "string",
- "example": "974761412044437307"
- },
- "discordAvatar": {
- "type": "string",
- "example": "6226b17aebc27a4a8d1ce04b"
- },
- "badgeIds": {
- "type": "array",
- "items": {
- "type": "integer",
- "example": 1
- }
- }
- }
- },
- "Results": {
- "type": "array",
- "items": {
- "$ref": "#/definitions/Result"
- }
- },
- "Result": {
- "type": "object",
- "properties": {
- "_id": {
- "type": "string",
- "example": "6226b17aebc27a4a8d1ce04b"
- },
- "wpm": {
- "type": "number",
- "format": "double",
- "example": 154.84
- },
- "rawWpm": {
- "type": "number",
- "format": "double",
- "example": 154.84
- },
- "charStats": {
- "type": "array",
- "items": {
- "type": "number"
- },
- "example": [44, 0, 0, 0]
- },
- "acc": {
- "type": "number",
- "format": "double",
- "example": 100
- },
- "mode": {
- "type": "string",
- "example": "words"
- },
- "mode2": {
- "type": "string",
- "example": "10"
- },
- "quoteLength": {
- "type": "integer",
- "example": -1
- },
- "timestamp": {
- "type": "integer",
- "example": 1651141719000
- },
- "restartCount": {
- "type": "integer",
- "example": 0
- },
- "incompleteTestSeconds": {
- "type": "number",
- "format": "double",
- "example": 14.5
- },
- "tags": {
- "type": "array",
- "items": {
- "type": "string"
- },
- "example": ["6210edbfc4fdc8a1700e648b"]
- },
- "consistency": {
- "type": "number",
- "format": "double",
- "example": 78.68
- },
- "keyConsistency": {
- "type": "number",
- "format": "double",
- "example": 60.22
- },
- "chartData": {
- "type": "object",
- "properties": {
- "wpm": {
- "type": "array",
- "items": {
- "type": "number"
- },
- "example": [144, 144, 144, 154]
- },
- "raw": {
- "type": "array",
- "items": {
- "type": "number"
- },
- "example": [150, 148, 124, 114]
- },
- "err": {
- "type": "array",
- "items": {
- "type": "number"
- },
- "example": [0, 0, 0, 0]
- }
- }
- },
- "testDuration": {
- "type": "number",
- "format": "double",
- "example": 3.41
- },
- "afkDuration": {
- "type": "number",
- "format": "double",
- "example": 0
- },
- "keySpacingStats": {
- "type": "object",
- "properties": {
- "average": {
- "type": "number",
- "format": "double",
- "example": 77.61
- },
- "sd": {
- "type": "number",
- "format": "double",
- "example": 33.31
- }
- }
- },
- "keyDurationStats": {
- "type": "object",
- "properties": {
- "average": {
- "type": "number",
- "format": "double",
- "example": 42.01
- },
- "sd": {
- "type": "number",
- "format": "double",
- "example": 19.65
- }
- }
- }
- }
- },
"CurrentTestActivity": {
"type": "object",
"properties": {
diff --git a/backend/src/init/configuration.ts b/backend/src/init/configuration.ts
index 72ed34d4f2d6..ffe5ebc052e6 100644
--- a/backend/src/init/configuration.ts
+++ b/backend/src/init/configuration.ts
@@ -4,12 +4,15 @@ import { ObjectId } from "mongodb";
import Logger from "../utils/logger";
import { identity } from "../utils/misc";
import { BASE_CONFIGURATION } from "../constants/base-configuration";
+import { Configuration } from "@monkeytype/contracts/schemas/configuration";
+import { addLog } from "../dal/logs";
+import { PartialConfiguration } from "@monkeytype/contracts/configuration";
const CONFIG_UPDATE_INTERVAL = 10 * 60 * 1000; // 10 Minutes
function mergeConfigurations(
- baseConfiguration: SharedTypes.Configuration,
- liveConfiguration: Partial
+ baseConfiguration: Configuration,
+ liveConfiguration: PartialConfiguration
): void {
if (
!_.isPlainObject(baseConfiguration) ||
@@ -45,7 +48,7 @@ let serverConfigurationUpdated = false;
export async function getCachedConfiguration(
attemptCacheUpdate = false
-): Promise {
+): Promise {
if (
attemptCacheUpdate &&
lastFetchTime < Date.now() - CONFIG_UPDATE_INTERVAL
@@ -57,7 +60,7 @@ export async function getCachedConfiguration(
return configuration;
}
-export async function getLiveConfiguration(): Promise {
+export async function getLiveConfiguration(): Promise {
lastFetchTime = Date.now();
const configurationCollection = db.collection("configuration");
@@ -71,7 +74,7 @@ export async function getLiveConfiguration(): Promise
const liveConfigurationWithoutId = _.omit(
liveConfiguration,
"_id"
- ) as SharedTypes.Configuration;
+ ) as Configuration;
mergeConfigurations(baseConfiguration, liveConfigurationWithoutId);
await pushConfiguration(baseConfiguration);
@@ -83,7 +86,7 @@ export async function getLiveConfiguration(): Promise
}); // Seed the base configuration.
}
} catch (error) {
- void Logger.logToDb(
+ void addLog(
"fetch_configuration_failure",
`Could not fetch configuration: ${error.message}`
);
@@ -92,9 +95,7 @@ export async function getLiveConfiguration(): Promise
return configuration;
}
-async function pushConfiguration(
- configuration: SharedTypes.Configuration
-): Promise {
+async function pushConfiguration(configuration: Configuration): Promise {
if (serverConfigurationUpdated) {
return;
}
@@ -103,7 +104,7 @@ async function pushConfiguration(
await db.collection("configuration").replaceOne({}, configuration);
serverConfigurationUpdated = true;
} catch (error) {
- void Logger.logToDb(
+ void addLog(
"push_configuration_failure",
`Could not push configuration: ${error.message}`
);
@@ -111,7 +112,7 @@ async function pushConfiguration(
}
export async function patchConfiguration(
- configurationUpdates: Partial
+ configurationUpdates: PartialConfiguration
): Promise