From 16e461385e385f7740a0874aa122bac7275b2f66 Mon Sep 17 00:00:00 2001 From: Mrugesh Mohapatra <1884376+raisedadead@users.noreply.github.com> Date: Tue, 27 May 2025 09:56:46 +0530 Subject: [PATCH] chore(api-server): bye-bye you served us well (#60520) Co-authored-by: Oliver Eyton-Williams --- .github/labeler.yml | 6 - .github/workflows/deploy-legacy.yml | 93 +- .github/workflows/e2e-playwright.yml | 108 +- .github/workflows/e2e-third-party.yml | 136 +- .gitignore | 4 +- .prettierignore | 1 - api-server/.babelrc | 16 - api-server/.gitignore | 51 - api-server/config/cors-settings.js | 8 - api-server/config/secrets.js | 96 - api-server/ecosystem.config.js | 35 - api-server/package.json | 89 - api-server/src/common/config.global.js | 7 - api-server/src/common/index.less | 3 - .../src/common/models/User-Credential.js | 94 - .../src/common/models/User-Credential.json | 16 - api-server/src/common/models/User-Identity.js | 158 - .../src/common/models/User-Identity.json | 23 - .../src/common/models/User-Identity.test.js | 29 - api-server/src/common/models/block.js | 9 - api-server/src/common/models/block.json | 53 - api-server/src/common/models/challenge.json | 135 - api-server/src/common/models/user.js | 937 ----- api-server/src/common/models/user.json | 520 --- api-server/src/common/utils/auth.js | 80 - .../src/common/utils/constantStrings.json | 9 - .../src/common/utils/empty-protector.js | 13 - api-server/src/common/utils/flash.js | 8 - api-server/src/common/utils/index.js | 34 - api-server/src/development-start.js | 21 - api-server/src/production-start.js | 31 - .../src/server/boot/a-extend-built-ins.js | 15 - .../src/server/boot/a-increase-listeners.js | 5 - api-server/src/server/boot/authentication.js | 246 -- api-server/src/server/boot/certificate.js | 569 --- api-server/src/server/boot/challenge.js | 1031 ------ api-server/src/server/boot/donate.js | 189 - api-server/src/server/boot/explorer.js | 33 - api-server/src/server/boot/news.js | 41 - api-server/src/server/boot/randomAPIs.js | 242 -- api-server/src/server/boot/restApi.js | 5 - api-server/src/server/boot/settings.js | 373 -- api-server/src/server/boot/status.js | 6 - api-server/src/server/boot/t-wiki.js | 10 - api-server/src/server/boot/user.js | 535 --- api-server/src/server/boot/z-not-found.js | 19 - api-server/src/server/boot_tests/README.md | 3 - .../src/server/boot_tests/certificate.test.js | 94 - .../src/server/boot_tests/challenge.test.js | 447 --- api-server/src/server/boot_tests/fixtures.js | 266 -- .../src/server/boot_tests/settings.test.js | 175 - api-server/src/server/component-passport.js | 180 - api-server/src/server/config.development.js | 9 - api-server/src/server/config.json | 22 - api-server/src/server/config.local.js | 11 - api-server/src/server/config.production.js | 3 - .../src/server/datasources.development.js | 30 - api-server/src/server/datasources.json | 11 - .../src/server/datasources.production.js | 21 - api-server/src/server/index.js | 116 - api-server/src/server/manifests/README.md | 8 - api-server/src/server/middleware.json | 59 - .../server/middlewares/constant-headers.js | 21 - .../src/server/middlewares/cookie-parser.js | 4 - api-server/src/server/middlewares/csp.js | 93 - .../server/middlewares/csurf-error-handler.js | 12 - .../server/middlewares/csurf-set-cookie.js | 13 - api-server/src/server/middlewares/csurf.js | 26 - .../src/server/middlewares/error-handlers.js | 68 - .../server/middlewares/express-extensions.js | 23 - .../src/server/middlewares/flash-cheaters.js | 32 - .../src/server/middlewares/ms-username.js | 20 - .../src/server/middlewares/passport-login.js | 21 - .../src/server/middlewares/rate-limit.js | 23 - .../middlewares/request-authorization.js | 106 - .../middlewares/request-authorization.test.js | 180 - .../middlewares/sentry-error-handler.js | 23 - .../middlewares/sentry-request-handler.js | 8 - .../middlewares/sentry-tracing-handler.js | 8 - api-server/src/server/middlewares/sessions.js | 20 - api-server/src/server/middlewares/survey.js | 42 - .../src/server/middlewares/user-token.js | 31 - api-server/src/server/model-config.json | 78 - api-server/src/server/models/auth-token.js | 15 - api-server/src/server/models/auth-token.json | 13 - api-server/src/server/models/donation.js | 71 - api-server/src/server/models/donation.json | 71 - api-server/src/server/models/exam.json | 70 - api-server/src/server/models/ms-username.json | 20 - api-server/src/server/models/survey.json | 41 - api-server/src/server/models/user-token.json | 20 - api-server/src/server/passport-providers.js | 48 - api-server/src/server/rss/index.js | 78 - api-server/src/server/rss/lybsyn.js | 48 - api-server/src/server/utils/__mocks__/exam.js | 91 - api-server/src/server/utils/auth.js | 50 - .../src/server/utils/cast-to-observable.js | 11 - api-server/src/server/utils/certTypes.json | 21 - api-server/src/server/utils/cookieConfig.js | 6 - .../src/server/utils/create-handled-error.js | 29 - api-server/src/server/utils/date-utils.js | 12 - .../src/server/utils/date-utils.test.js | 87 - .../src/server/utils/disabled-endpoints.js | 17 - api-server/src/server/utils/donation.js | 181 - api-server/src/server/utils/donation.test.js | 59 - api-server/src/server/utils/exam-schemas.js | 160 - api-server/src/server/utils/exam.js | 89 - api-server/src/server/utils/exam.test.js | 54 - api-server/src/server/utils/get-curriculum.js | 21 - .../src/server/utils/getSetAccessToken.js | 73 - .../server/utils/getSetAccessToken.test.js | 126 - .../src/server/utils/in-memory-cache.js | 43 - .../src/server/utils/in-memory-cache.test.js | 65 - .../src/server/utils/lang-passthrough-urls.js | 4 - api-server/src/server/utils/middleware.js | 108 - .../src/server/utils/publicUserProps.js | 79 - api-server/src/server/utils/react.js | 6 - api-server/src/server/utils/redirection.js | 83 - .../src/server/utils/redirection.test.js | 121 - api-server/src/server/utils/rx.js | 58 - api-server/src/server/utils/stripeHelpers.js | 10 - api-server/src/server/utils/url-utils.js | 3 - api-server/src/server/utils/user-stats.js | 109 - .../src/server/utils/user-stats.test.js | 623 ---- api-server/src/server/utils/validators.js | 30 - .../src/server/views/emails/a-new-user.ejs | 9 - .../src/server/views/emails/certified.ejs | 15 - .../views/emails/user-request-sign-in.ejs | 9 - .../views/emails/user-request-sign-up.ejs | 13 - .../emails/user-request-update-email.ejs | 7 - api/package.json | 1 - babel.config.js | 2 +- eslint.config.mjs | 1 - jest.config.js | 2 +- knip.jsonc | 1 - package.json | 8 +- pnpm-lock.yaml | 3181 +---------------- pnpm-workspace.yaml | 1 - tools/scripts/lint/validate-keys.ts | 15 +- 139 files changed, 140 insertions(+), 14498 deletions(-) delete mode 100644 api-server/.babelrc delete mode 100644 api-server/.gitignore delete mode 100644 api-server/config/cors-settings.js delete mode 100644 api-server/config/secrets.js delete mode 100644 api-server/ecosystem.config.js delete mode 100644 api-server/package.json delete mode 100644 api-server/src/common/config.global.js delete mode 100644 api-server/src/common/index.less delete mode 100644 api-server/src/common/models/User-Credential.js delete mode 100644 api-server/src/common/models/User-Credential.json delete mode 100644 api-server/src/common/models/User-Identity.js delete mode 100644 api-server/src/common/models/User-Identity.json delete mode 100644 api-server/src/common/models/User-Identity.test.js delete mode 100644 api-server/src/common/models/block.js delete mode 100644 api-server/src/common/models/block.json delete mode 100644 api-server/src/common/models/challenge.json delete mode 100644 api-server/src/common/models/user.js delete mode 100644 api-server/src/common/models/user.json delete mode 100644 api-server/src/common/utils/auth.js delete mode 100644 api-server/src/common/utils/constantStrings.json delete mode 100644 api-server/src/common/utils/empty-protector.js delete mode 100644 api-server/src/common/utils/flash.js delete mode 100644 api-server/src/common/utils/index.js delete mode 100644 api-server/src/development-start.js delete mode 100644 api-server/src/production-start.js delete mode 100644 api-server/src/server/boot/a-extend-built-ins.js delete mode 100644 api-server/src/server/boot/a-increase-listeners.js delete mode 100644 api-server/src/server/boot/authentication.js delete mode 100644 api-server/src/server/boot/certificate.js delete mode 100644 api-server/src/server/boot/challenge.js delete mode 100644 api-server/src/server/boot/donate.js delete mode 100644 api-server/src/server/boot/explorer.js delete mode 100644 api-server/src/server/boot/news.js delete mode 100644 api-server/src/server/boot/randomAPIs.js delete mode 100644 api-server/src/server/boot/restApi.js delete mode 100644 api-server/src/server/boot/settings.js delete mode 100644 api-server/src/server/boot/status.js delete mode 100644 api-server/src/server/boot/t-wiki.js delete mode 100644 api-server/src/server/boot/user.js delete mode 100644 api-server/src/server/boot/z-not-found.js delete mode 100644 api-server/src/server/boot_tests/README.md delete mode 100644 api-server/src/server/boot_tests/certificate.test.js delete mode 100644 api-server/src/server/boot_tests/challenge.test.js delete mode 100644 api-server/src/server/boot_tests/fixtures.js delete mode 100644 api-server/src/server/boot_tests/settings.test.js delete mode 100644 api-server/src/server/component-passport.js delete mode 100644 api-server/src/server/config.development.js delete mode 100644 api-server/src/server/config.json delete mode 100644 api-server/src/server/config.local.js delete mode 100644 api-server/src/server/config.production.js delete mode 100644 api-server/src/server/datasources.development.js delete mode 100644 api-server/src/server/datasources.json delete mode 100644 api-server/src/server/datasources.production.js delete mode 100644 api-server/src/server/index.js delete mode 100644 api-server/src/server/manifests/README.md delete mode 100644 api-server/src/server/middleware.json delete mode 100644 api-server/src/server/middlewares/constant-headers.js delete mode 100644 api-server/src/server/middlewares/cookie-parser.js delete mode 100644 api-server/src/server/middlewares/csp.js delete mode 100644 api-server/src/server/middlewares/csurf-error-handler.js delete mode 100644 api-server/src/server/middlewares/csurf-set-cookie.js delete mode 100644 api-server/src/server/middlewares/csurf.js delete mode 100644 api-server/src/server/middlewares/error-handlers.js delete mode 100644 api-server/src/server/middlewares/express-extensions.js delete mode 100644 api-server/src/server/middlewares/flash-cheaters.js delete mode 100644 api-server/src/server/middlewares/ms-username.js delete mode 100644 api-server/src/server/middlewares/passport-login.js delete mode 100644 api-server/src/server/middlewares/rate-limit.js delete mode 100644 api-server/src/server/middlewares/request-authorization.js delete mode 100644 api-server/src/server/middlewares/request-authorization.test.js delete mode 100644 api-server/src/server/middlewares/sentry-error-handler.js delete mode 100644 api-server/src/server/middlewares/sentry-request-handler.js delete mode 100644 api-server/src/server/middlewares/sentry-tracing-handler.js delete mode 100644 api-server/src/server/middlewares/sessions.js delete mode 100644 api-server/src/server/middlewares/survey.js delete mode 100644 api-server/src/server/middlewares/user-token.js delete mode 100644 api-server/src/server/model-config.json delete mode 100644 api-server/src/server/models/auth-token.js delete mode 100644 api-server/src/server/models/auth-token.json delete mode 100644 api-server/src/server/models/donation.js delete mode 100644 api-server/src/server/models/donation.json delete mode 100644 api-server/src/server/models/exam.json delete mode 100644 api-server/src/server/models/ms-username.json delete mode 100644 api-server/src/server/models/survey.json delete mode 100644 api-server/src/server/models/user-token.json delete mode 100644 api-server/src/server/passport-providers.js delete mode 100644 api-server/src/server/rss/index.js delete mode 100644 api-server/src/server/rss/lybsyn.js delete mode 100644 api-server/src/server/utils/__mocks__/exam.js delete mode 100644 api-server/src/server/utils/auth.js delete mode 100644 api-server/src/server/utils/cast-to-observable.js delete mode 100644 api-server/src/server/utils/certTypes.json delete mode 100644 api-server/src/server/utils/cookieConfig.js delete mode 100644 api-server/src/server/utils/create-handled-error.js delete mode 100644 api-server/src/server/utils/date-utils.js delete mode 100644 api-server/src/server/utils/date-utils.test.js delete mode 100644 api-server/src/server/utils/disabled-endpoints.js delete mode 100644 api-server/src/server/utils/donation.js delete mode 100644 api-server/src/server/utils/donation.test.js delete mode 100644 api-server/src/server/utils/exam-schemas.js delete mode 100644 api-server/src/server/utils/exam.js delete mode 100644 api-server/src/server/utils/exam.test.js delete mode 100644 api-server/src/server/utils/get-curriculum.js delete mode 100644 api-server/src/server/utils/getSetAccessToken.js delete mode 100644 api-server/src/server/utils/getSetAccessToken.test.js delete mode 100644 api-server/src/server/utils/in-memory-cache.js delete mode 100644 api-server/src/server/utils/in-memory-cache.test.js delete mode 100644 api-server/src/server/utils/lang-passthrough-urls.js delete mode 100644 api-server/src/server/utils/middleware.js delete mode 100644 api-server/src/server/utils/publicUserProps.js delete mode 100644 api-server/src/server/utils/react.js delete mode 100644 api-server/src/server/utils/redirection.js delete mode 100644 api-server/src/server/utils/redirection.test.js delete mode 100644 api-server/src/server/utils/rx.js delete mode 100644 api-server/src/server/utils/stripeHelpers.js delete mode 100644 api-server/src/server/utils/url-utils.js delete mode 100644 api-server/src/server/utils/user-stats.js delete mode 100644 api-server/src/server/utils/user-stats.test.js delete mode 100644 api-server/src/server/utils/validators.js delete mode 100644 api-server/src/server/views/emails/a-new-user.ejs delete mode 100644 api-server/src/server/views/emails/certified.ejs delete mode 100644 api-server/src/server/views/emails/user-request-sign-in.ejs delete mode 100644 api-server/src/server/views/emails/user-request-sign-up.ejs delete mode 100644 api-server/src/server/views/emails/user-request-update-email.ejs diff --git a/.github/labeler.yml b/.github/labeler.yml index 0e7b0ccc9fe..3dd43e316ac 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,6 +1,3 @@ -'scope: docs': - - docs/**/* - 'scope: curriculum': - curriculum/challenges/**/* @@ -8,7 +5,6 @@ - client/**/* 'platform: api': - - api-server/**/* - api/**/* 'scope: tools/scripts': @@ -18,8 +14,6 @@ - e2e/**/* 'scope: i18n': - - any: ['curriculum/challenges/**/*', '!curriculum/challenges/english/**/*'] - - docs/i18n/**/* - client/i18n/**/* - config/crowdin/**/* - shared/config/i18n/**/* diff --git a/.github/workflows/deploy-legacy.yml b/.github/workflows/deploy-legacy.yml index c46b0f446f0..0fe872fd1a3 100644 --- a/.github/workflows/deploy-legacy.yml +++ b/.github/workflows/deploy-legacy.yml @@ -1,4 +1,4 @@ -name: CD - Deploy API (Legacy) & Clients +name: CD - Deploy - Clients on: workflow_dispatch: @@ -33,98 +33,9 @@ jobs: ;; esac - api: - name: API (Legacy) - [${{ needs.setup-jobs.outputs.tgt_env_short }}] - needs: [setup-jobs] - runs-on: ubuntu-22.04 - permissions: - deployments: write - contents: read - environment: - name: ${{ needs.setup-jobs.outputs.tgt_env_short }}-api-legacy - env: - TS_USERNAME: ${{ secrets.TS_USERNAME }} - TS_MACHINE_NAME_PREFIX: ${{ secrets.TS_MACHINE_NAME_PREFIX }} - steps: - - name: Setup and connect to Tailscale network - uses: tailscale/github-action@v3 - with: - oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }} - oauth-secret: ${{ secrets.TS_OAUTH_SECRET }} - hostname: gha-${{needs.setup-jobs.outputs.tgt_env_short}}-api-legacy-ci-${{ github.run_id }} - tags: tag:ci - version: latest - - - name: Configure SSH & Check Connection - run: | - mkdir -p ~/.ssh - echo "Host * - UserKnownHostsFile=/dev/null - StrictHostKeyChecking no" > ~/.ssh/config - chmod 644 ~/.ssh/config - sleep 10 - for i in {1..3}; do - TS_MACHINE_NAME=${TS_MACHINE_NAME_PREFIX}-api-${i} - tailscale status | grep -q "$TS_MACHINE_NAME" || { echo "Machine not found"; exit 1; } - sleep 1 - MACHINE_IP=$(tailscale ip -4 $TS_MACHINE_NAME) - ssh $TS_USERNAME@$MACHINE_IP "uptime" - done - - - name: Deploy API - # [NOTE] Use backslashes for expansion on remote machine, example: \$NVM_DIR, etc. - run: | - GIT_SOURCE_BRANCH=${{ needs.setup-jobs.outputs.tgt_env_branch }} - for i in {1..3}; do - TS_MACHINE_NAME=${TS_MACHINE_NAME_PREFIX}-api-${i} - REMOTE_SCRIPT=" - set -e - echo -e '\nLOG:Deploying API (Legacy) to $TS_MACHINE_NAME...' - - echo -e '\nLOG:Environment setup...' - cd /home/$TS_USERNAME/freeCodeCamp - export NVM_DIR=\$HOME/.nvm && [ -s "\$NVM_DIR/nvm.sh" ] && source "\$NVM_DIR/nvm.sh" - echo -e '\nLOG:Checking available Node.js versions...' - nvm ls | grep 'default' - echo -e '\nLOG:Checking Node.js version...' - node --version - - echo -e '\nLOG:Stopping all PM2 services...' - pm2 stop all - - echo -e '\nLOG:Git operations...' - git status - git clean -f - git fetch --all --prune - git checkout -f $GIT_SOURCE_BRANCH - git reset --hard origin/$GIT_SOURCE_BRANCH - git status - - echo -e '\nLOG:Building...' - npm i -g pnpm@10 - pnpm clean:packages - pnpm clean:server - pnpm install - pnpm prebuild - pnpm build:curriculum - pnpm build:server - echo -e '\nLOG:Build completed.' - - echo -e '\nLOG:Starting PM2 reload...' - pm2 reload './api-server/ecosystem.config.js' - echo -e '\nLOG:PM2 reload completed.' - - pm2 ls - pm2 save - echo -e '\nLOG:Finished deployment.' - " - MACHINE_IP=$(tailscale ip -4 $TS_MACHINE_NAME) - ssh $TS_USERNAME@$MACHINE_IP "$REMOTE_SCRIPT" - done - client: name: Clients - [${{ needs.setup-jobs.outputs.tgt_env_short }}] [${{ matrix.lang-name-short }}] - needs: [setup-jobs, api] + needs: [setup-jobs] runs-on: ubuntu-22.04 strategy: fail-fast: false diff --git a/.github/workflows/e2e-playwright.yml b/.github/workflows/e2e-playwright.yml index 87363f41585..7af30c7718d 100644 --- a/.github/workflows/e2e-playwright.yml +++ b/.github/workflows/e2e-playwright.yml @@ -72,8 +72,8 @@ jobs: name: webpack-stats path: client/public/stats.json - build-new-api: - name: Build New Api (Container) + build-api: + name: Build API (Container) runs-on: ubuntu-22.04 steps: @@ -91,108 +91,16 @@ jobs: - name: Save Image run: docker save fcc-api > api-artifact.tar - - name: Upload Api Artifact + - name: Upload API Artifact uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 with: name: api-artifact path: api-artifact.tar - playwright-run-old-api: - name: E2E Test + playwright-run-api: + name: Run Playwright Tests runs-on: ubuntu-22.04 - needs: build-client - strategy: - fail-fast: false - matrix: - # Not Mobile Safari until we can get it working. Webkit and Mobile - # Chrome both work, so hopefully there are no Mobile Safari specific - # bugs. - browsers: [chromium, firefox, webkit, Mobile Chrome] - node-version: [20] - - services: - mongodb: - image: mongo:8.0 - ports: - - 27017:27017 - # We need mailhog to catch any emails the api tries to send. - mailhog: - image: mailhog/mailhog - ports: - - 1025:1025 # SMTP server (listens for emails) - - 8025:8025 # HTTP server (so we can make requests to the api) - - steps: - - name: Set Action Environment Variables - run: | - echo "GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_ENV - - - name: Checkout Source Files - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - with: - submodules: 'recursive' - - - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 - with: - name: client-artifact - - - name: Unpack Client Artifact - run: | - tar -xf client-artifact.tar - rm client-artifact.tar - - - name: Setup pnpm - uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d #v3.0.0 - - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 - with: - node-version: ${{ matrix.node-version }} - - - name: Set freeCodeCamp Environment Variables - run: | - cp sample.env .env - - - name: Install and Build - run: | - pnpm install - pnpm run create:shared - pnpm run build:curriculum - pnpm run build:server - - - name: Seed Database with Certified User - run: pnpm run seed:certified-user - - # start-ci uses pm2, so it needs to be installed globally - - name: Install pm2 - run: npm i -g pm2 - - - name: Install playwright dependencies - run: npx playwright install --with-deps - - - name: Run playwright tests - run: | - pnpm run start-ci & - sleep 10 - npx playwright test --project="${{ matrix.browsers }}" --grep-invert 'third-party-donation.spec.ts' - - uses: actions/upload-artifact@v4 - if: ${{ !cancelled() }} - with: - name: playwright-report-old-api-${{ matrix.browsers }} - path: playwright/reporter - retention-days: 30 - - name: Upload screenshots - if: failure() - uses: actions/upload-artifact@v4 - with: - name: screenshots-old-api-${{ matrix.browsers }} - path: playwright/test-results - retention-days: 14 - - playwright-run-new-api: - name: Run Playwright Tests (with new Api) - runs-on: ubuntu-22.04 - needs: [build-client, build-new-api] + needs: [build-client, build-api] strategy: fail-fast: false matrix: @@ -215,7 +123,7 @@ jobs: tar -xf client-artifact/client-artifact.tar rm client-artifact/client-artifact.tar - - name: Load Api Image + - name: Load API Image run: | docker load < api-artifact/api-artifact.tar rm api-artifact/api-artifact.tar @@ -261,4 +169,4 @@ jobs: with: name: playwright-report-${{ matrix.browsers }} path: playwright/reporter - retention-days: 30 + retention-days: 7 diff --git a/.github/workflows/e2e-third-party.yml b/.github/workflows/e2e-third-party.yml index 027b7ed6f3e..c21da94ff7c 100644 --- a/.github/workflows/e2e-third-party.yml +++ b/.github/workflows/e2e-third-party.yml @@ -11,23 +11,12 @@ concurrency: cancel-in-progress: ${{ !contains(github.ref, 'main') && !contains(github.ref, 'prod-') }} jobs: - do-everything: - name: Build & Test + build-client: + name: Build Client runs-on: ubuntu-22.04 strategy: matrix: node-version: [20] - services: - mongodb: - image: mongo:8.0 - ports: - - 27017:27017 - # We need mailhog to catch any emails the api tries to send. - mailhog: - image: mailhog/mailhog - ports: - - 1025:1025 - steps: - name: Checkout Source Files uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 @@ -47,8 +36,7 @@ jobs: uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: ${{ matrix.node-version }} - # cypress-io/github-action caches the store, so we should not cache it - # here. + cache: pnpm - name: Set freeCodeCamp Environment Variables run: | @@ -60,32 +48,124 @@ jobs: - name: Install and Build run: | pnpm install - pnpm run create:shared - pnpm run build:curriculum - pnpm run build:server - - - name: Seed Database with Certified User - run: pnpm run seed:certified-user + pnpm run build - name: Move serve.json to Public Folder run: cp client-config/serve.json client/public/serve.json - # start-ci uses pm2, so it needs to be installed globally - - name: Install pm2 - run: npm i -g pm2 + - name: Tar Files + run: tar -cf client-artifact.tar client/public + + - name: Upload Client Artifact + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: client-artifact + path: client-artifact.tar + + build-api: + name: Build API (Container) + runs-on: ubuntu-22.04 + steps: + - name: Checkout Source Files + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + submodules: 'recursive' + + - name: Create Image + run: | + docker build \ + -t fcc-api \ + -f docker/api/Dockerfile . + + - name: Save Image + run: docker save fcc-api > api-artifact.tar + + - name: Upload API Artifact + uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1 + with: + name: api-artifact + path: api-artifact.tar + + playwright-run-api: + name: Run Playwright 3rd Party Donation Tests + runs-on: ubuntu-22.04 + needs: [build-client, build-api] + strategy: + fail-fast: false + matrix: + browsers: [chromium] + node-version: [20] + services: + mongodb: + image: mongo:8.0 + ports: + - 27017:27017 + mailhog: + image: mailhog/mailhog + ports: + - 1025:1025 + steps: + - name: Set Action Environment Variables + run: | + echo "GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_ENV + + - name: Checkout Source Files + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4 + + - name: Unpack Client Artifact + run: | + tar -xf client-artifact/client-artifact.tar + rm client-artifact/client-artifact.tar + + - name: Load API Image + run: | + docker load < api-artifact/api-artifact.tar + rm api-artifact/api-artifact.tar + + - name: Setup pnpm + uses: pnpm/action-setup@a3252b78c470c02df07e9d59298aecedc3ccdd6d #v3.0.0 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + with: + node-version: ${{ matrix.node-version }} + + - name: Install Dependencies + run: pnpm install + + - name: Set freeCodeCamp Environment Variables (needed by api) + run: | + sed '/STRIPE/d; /PAYPAL/d; /PATREON/d;' sample.env > .env + echo 'STRIPE_PUBLIC_KEY=${{ secrets.STRIPE_PUBLIC_KEY }}' >> .env + echo 'PAYPAL_CLIENT_ID=${{ secrets.PAYPAL_CLIENT_ID }}' >> .env + echo 'PATREON_CLIENT_ID=${{ secrets.PATREON_CLIENT_ID }}' >> .env - name: Install playwright dependencies run: npx playwright install --with-deps - - name: Run playwright tests + - name: Install and Build run: | - pnpm run start-ci & + pnpm install + pnpm run create:shared + pnpm run build:curriculum + + - name: Start apps + run: | + docker compose up -d + pnpm run serve:client-ci & sleep 10 - npx playwright test third-party-donation.spec.ts --project=${{ matrix.browsers }} + + - name: Seed Database with Certified User + run: pnpm run seed:certified-user + + - name: Run playwright tests + run: npx playwright test third-party-donation.spec.ts --project=${{ matrix.browsers }} - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} with: name: playwright-report-${{ matrix.browsers }} path: playwright/reporter - retention-days: 30 + retention-days: 7 diff --git a/.gitignore b/.gitignore index 6dcea9464cc..b600541d6a3 100644 --- a/.gitignore +++ b/.gitignore @@ -63,7 +63,6 @@ $RECYCLE.BIN/ # Icon must end with two \r Icon - # Thumbnails ._* @@ -172,7 +171,7 @@ utils/slugs.test.js ### vim ### # Swap [._]*.s[a-v][a-z] -!*.svg # comment out if you don't need vector files +!*.svg # comment out if you don't need vector files [._]*.sw[a-p] [._]s[a-rt-v][a-z] [._]ss[a-gi-z] @@ -198,7 +197,6 @@ tags curriculum/curricula.json ### Additional Folders ### -api-server/lib/* curriculum/dist curriculum/build diff --git a/.prettierignore b/.prettierignore index a830f045cb2..b940bf181fd 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,6 +1,5 @@ **/.cache **/*fixtures* -api-server/lib client/**/trending.json client/**/search-bar.json client/config/*.json diff --git a/api-server/.babelrc b/api-server/.babelrc deleted file mode 100644 index 1e7ee63b3b2..00000000000 --- a/api-server/.babelrc +++ /dev/null @@ -1,16 +0,0 @@ -{ - "presets": [ - [ - "@babel/preset-env", - { - "targets": { - "node": 10 - } - } - ] - ], - "plugins": [ - "@babel/plugin-proposal-object-rest-spread", - "@babel/plugin-proposal-optional-chaining" - ] -} diff --git a/api-server/.gitignore b/api-server/.gitignore deleted file mode 100644 index ed39e1d9f87..00000000000 --- a/api-server/.gitignore +++ /dev/null @@ -1,51 +0,0 @@ -lib-cov -*~ -*.seed -*.log -*.csv -*.dat -*.out -*.pid -*.gz -*.swp -.floo -.flooignore -builtAssets/ -pm2.js -*.env -pids -logs -results -tmp -.ds_store -.vscode - -npm-debug.log -node_modules -compiled -.idea -*.iml -.DS_Store -Thumbs.db -bower_components -main.css -bundle.js -coverage -.remote-sync.json -.tern-project - -server/*.bundle.js -public/js/bundle* -seed/unpacked - -*.map - -// revision manifest -server/rev-manifest.json -server/manifests/* -!server/manifests/README.md - -webpack-bundle-stats.html -server/rev-manifest.json -google-credentials.json -.vs/* diff --git a/api-server/config/cors-settings.js b/api-server/config/cors-settings.js deleted file mode 100644 index aeb15aba457..00000000000 --- a/api-server/config/cors-settings.js +++ /dev/null @@ -1,8 +0,0 @@ -exports.allowedOrigins = [ - 'https://www.freecodecamp.dev', - 'https://www.freecodecamp.org', - 'https://beta.freecodecamp.dev', - 'https://beta.freecodecamp.org', - 'https://chinese.freecodecamp.dev', - 'https://chinese.freecodecamp.org' -]; diff --git a/api-server/config/secrets.js b/api-server/config/secrets.js deleted file mode 100644 index 2f7c1df1bf6..00000000000 --- a/api-server/config/secrets.js +++ /dev/null @@ -1,96 +0,0 @@ -const { - MONGODB, - MONGOHQ_URL, - - SESSION_SECRET, - COOKIE_SECRET, - JWT_SECRET, - - AUTH0_CLIENT_ID, - AUTH0_CLIENT_SECRET, - AUTH0_DOMAIN, - - FACEBOOK_ID, - FACEBOOK_SECRET, - - GITHUB_ID, - GITHUB_SECRET, - - GOOGLE_ID, - GOOGLE_SECRET, - - LINKEDIN_ID, - LINKEDIN_SECRET, - - TWITTER_KEY, - TWITTER_SECRET, - TWITTER_TOKEN, - TWITTER_TOKEN_SECRET, - - SENTRY_DSN, - - STRIPE_PUBLIC_KEY, - STRIPE_SECRET_KEY -} = process.env; - -module.exports = { - db: MONGODB || MONGOHQ_URL, - - cookieSecret: COOKIE_SECRET, - jwtSecret: JWT_SECRET, - sessionSecret: SESSION_SECRET, - - auth0: { - clientID: AUTH0_CLIENT_ID, - clientSecret: AUTH0_CLIENT_SECRET, - domain: AUTH0_DOMAIN - }, - - facebook: { - clientID: FACEBOOK_ID, - clientSecret: FACEBOOK_SECRET, - callbackURL: '/auth/facebook/callback', - passReqToCallback: true - }, - - github: { - clientID: GITHUB_ID, - clientSecret: GITHUB_SECRET, - callbackURL: '/auth/github/callback', - passReqToCallback: true - }, - - twitter: { - consumerKey: TWITTER_KEY, - consumerSecret: TWITTER_SECRET, - token: TWITTER_TOKEN, - tokenSecret: TWITTER_TOKEN_SECRET, - callbackURL: '/auth/twitter/callback', - passReqToCallback: true - }, - - google: { - clientID: GOOGLE_ID, - clientSecret: GOOGLE_SECRET, - callbackURL: '/auth/google/callback', - passReqToCallback: true - }, - - linkedin: { - clientID: LINKEDIN_ID, - clientSecret: LINKEDIN_SECRET, - callbackURL: '/auth/linkedin/callback', - profileFields: ['public-profile-url'], - scope: ['r_basicprofile', 'r_emailaddress'], - passReqToCallback: true - }, - - sentry: { - dsn: SENTRY_DSN - }, - - stripe: { - public: STRIPE_PUBLIC_KEY, - secret: STRIPE_SECRET_KEY - } -}; diff --git a/api-server/ecosystem.config.js b/api-server/ecosystem.config.js deleted file mode 100644 index 592d95cbbcb..00000000000 --- a/api-server/ecosystem.config.js +++ /dev/null @@ -1,35 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const dotenv = require('dotenv'); - -const filePath = path.resolve(__dirname, '..', '.env'); -let env = {}; -try { - env = dotenv.parse(fs.readFileSync(filePath)); -} catch (e) { - console.log( - "If you're setting the env vars in the shell, it should be fine (you can probably ignore the error)." - ); - console.log(e); -} -// without this, loopback cannot find strong-error-handler. Node can, so we know -// there's no _real_ issue, but loopback is not able to find it. -const loopbackModuleResolutionHack = path.resolve( - __dirname, - '../node_modules/.pnpm/node_modules' -); - -module.exports = { - apps: [ - { - script: `./lib/production-start.js`, - cwd: __dirname, - env: { ...env, NODE_PATH: loopbackModuleResolutionHack }, - max_memory_restart: '600M', - instances: 'max', - exec_mode: 'cluster', - name: env.DEPLOYMENT_ENV === 'staging' ? 'dev' : 'org' - } - ] -}; diff --git a/api-server/package.json b/api-server/package.json deleted file mode 100644 index 1edcb6800d8..00000000000 --- a/api-server/package.json +++ /dev/null @@ -1,89 +0,0 @@ -{ - "name": "@freecodecamp/api-server", - "version": "0.0.1", - "description": "The freeCodeCamp.org open-source codebase and curriculum", - "license": "BSD-3-Clause", - "private": true, - "engines": { - "node": ">=16", - "pnpm": ">=10" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/freeCodeCamp/freeCodeCamp.git" - }, - "bugs": { - "url": "https://github.com/freeCodeCamp/freeCodeCamp/issues" - }, - "homepage": "https://github.com/freeCodeCamp/freeCodeCamp#readme", - "author": "freeCodeCamp ", - "main": "none", - "scripts": { - "babel-dev-server": "babel-node --inspect=0.0.0.0 ./src/server/index.js", - "prebuild": "pnpm common-setup", - "build": "babel src --out-dir lib --ignore '/**/*.test.js' --copy-files --no-copy-ignored", - "common-setup": "pnpm -w run create:shared", - "predevelop": "pnpm common-setup", - "develop": "node src/development-start.js", - "start": "DEBUG=fcc* node lib/production-start.js" - }, - "dependencies": { - "@freecodecamp/loopback-component-passport": "1.2.0", - "@sentry/node": "7.37.1", - "@sentry/tracing": "7.37.1", - "accepts": "1.3.8", - "body-parser": "1.20.0", - "compression": "1.7.4", - "connect-mongo": "3.2.0", - "cookie-parser": "1.4.6", - "cors": "2.8.5", - "csurf": "1.11.0", - "date-fns": "1.30.1", - "debug": "2.2.0", - "dedent": "0.7.0", - "dotenv": "6.2.0", - "express-flash": "0.0.2", - "express-rate-limit": "^6.7.0", - "express-session": "1.17.3", - "express-validator": "6.14.1", - "helmet": "3.23.3", - "helmet-csp": "2.10.0", - "joi": "17.9.2", - "joi-objectid": "3.0.1", - "jsonwebtoken": "8.5.1", - "lodash": "4.17.21", - "loopback": "3.28.0", - "loopback-boot": "2.28.0", - "loopback-connector-mongodb": "5.6.0", - "method-override": "3.0.0", - "moment": "2.29.3", - "moment-timezone": "0.5.33", - "mongodb": "3.6.9", - "morgan": "1.10.0", - "nanoid": "3.3.4", - "no-profanity": "^1.4.2", - "node-fetch": "^2.6.7", - "nodemailer-ses-transport": "1.5.1", - "passport": "0.4.1", - "passport-auth0": "1.4.2", - "passport-local": "1.0.0", - "passport-mock-strategy": "2.0.0", - "query-string": "6.14.0", - "rate-limit-mongo": "^2.3.2", - "rx": "4.1.0", - "stripe": "8.205.0", - "strong-error-handler": "3.5.0", - "uuid": "3.4.0", - "validator": "13.7.0" - }, - "devDependencies": { - "@babel/cli": "7.17.10", - "@babel/core": "7.18.0", - "@babel/node": "7.17.10", - "@babel/plugin-proposal-object-rest-spread": "7.18.0", - "@babel/plugin-proposal-optional-chaining": "7.17.12", - "@babel/preset-env": "7.18.0", - "loopback-component-explorer": "6.4.0", - "nodemon": "2.0.16" - } -} diff --git a/api-server/src/common/config.global.js b/api-server/src/common/config.global.js deleted file mode 100644 index fa435086c24..00000000000 --- a/api-server/src/common/config.global.js +++ /dev/null @@ -1,7 +0,0 @@ -// The path where to mount the REST API app -exports.restApiRoot = '/api'; -// -// The URL where the browser client can access the REST API is available -// Replace with a full url (including hostname) if your client is being -// served from a different server than your REST API. -exports.restApiUrl = exports.restApiRoot; diff --git a/api-server/src/common/index.less b/api-server/src/common/index.less deleted file mode 100644 index 7b49f5ac06e..00000000000 --- a/api-server/src/common/index.less +++ /dev/null @@ -1,3 +0,0 @@ -& { - @import './app/index.less'; -} diff --git a/api-server/src/common/models/User-Credential.js b/api-server/src/common/models/User-Credential.js deleted file mode 100644 index 1e5688d5e0a..00000000000 --- a/api-server/src/common/models/User-Credential.js +++ /dev/null @@ -1,94 +0,0 @@ -import debug from 'debug'; -import { Observable } from 'rx'; - -import { - createUserUpdatesFromProfile, - getSocialProvider -} from '../../server/utils/auth'; -import { observeMethod, observeQuery } from '../../server/utils/rx'; - -const log = debug('fcc:models:UserCredential'); -module.exports = function (UserCredential) { - UserCredential.link = function ( - userId, - _provider, - authScheme, - profile, - credentials, - options = {}, - cb - ) { - if (typeof options === 'function' && !cb) { - cb = options; - options = {}; - } - const User = UserCredential.app.models.User; - const findCred = observeMethod(UserCredential, 'findOne'); - const createCred = observeMethod(UserCredential, 'create'); - - const provider = getSocialProvider(_provider); - const query = { - where: { - provider: provider, - externalId: profile.id - } - }; - - // find createCred if they exist - // if not create it - // if yes, update credentials - // also if github - // update profile - // update username - // update picture - log('link query', query); - return findCred(query) - .flatMap(_credentials => { - const modified = new Date(); - const updateUser = new Promise((resolve, reject) => { - User.find({ id: userId }, (err, user) => { - if (err) { - return reject(err); - } - return user.updateAttributes( - createUserUpdatesFromProfile(provider, profile), - updateErr => { - if (updateErr) { - return reject(updateErr); - } - return resolve(); - } - ); - }); - }); - let updateCredentials; - if (!_credentials) { - updateCredentials = createCred({ - provider, - externalId: profile.id, - authScheme, - // we no longer want to keep the profile - // this is information we do not need or use - profile: null, - credentials, - userId, - created: modified, - modified - }); - } else { - _credentials.credentials = credentials; - updateCredentials = observeQuery(_credentials, 'updateAttributes', { - profile: null, - credentials, - modified - }); - } - return Observable.combineLatest( - Observable.fromPromise(updateUser), - updateCredentials, - (_, credentials) => credentials - ); - }) - .subscribe(credentials => cb(null, credentials), cb); - }; -}; diff --git a/api-server/src/common/models/User-Credential.json b/api-server/src/common/models/User-Credential.json deleted file mode 100644 index b3ed7d7e48d..00000000000 --- a/api-server/src/common/models/User-Credential.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "userCredential", - "plural": "userCredentials", - "base": "UserCredential", - "properties": {}, - "validations": [], - "relations": { - "user": { - "type": "belongsTo", - "model": "user", - "foreignKey": "userId" - } - }, - "acls": [], - "methods": {} -} diff --git a/api-server/src/common/models/User-Identity.js b/api-server/src/common/models/User-Identity.js deleted file mode 100644 index 8905c2edb69..00000000000 --- a/api-server/src/common/models/User-Identity.js +++ /dev/null @@ -1,158 +0,0 @@ -import dedent from 'dedent'; -import { Observable } from 'rx'; -// import debug from 'debug'; -import { isEmail } from 'validator'; - -import { wrapHandledError } from '../../server/utils/create-handled-error.js'; -import { observeMethod, observeQuery } from '../../server/utils/rx'; - -// const log = debug('fcc:models:userIdent'); - -export function ensureLowerCaseEmail(profile) { - return typeof profile?.emails?.[0]?.value === 'string' - ? profile.emails[0].value.toLowerCase() - : ''; -} - -export default function initializeUserIdent(UserIdent) { - UserIdent.on('dataSourceAttached', () => { - UserIdent.findOne$ = observeMethod(UserIdent, 'findOne'); - }); - - UserIdent.login = function ( - _provider, - authScheme, - profile, - credentials, - options, - cb - ) { - const User = UserIdent.app.models.User; - const AccessToken = UserIdent.app.models.AccessToken; - options = options || {}; - if (typeof options === 'function' && !cb) { - cb = options; - options = {}; - } - - // get the social provider data and the external id from auth0 - profile.id = profile.id || profile.openid; - const auth0IdString = '' + profile.id; - const [provider, socialExtId] = auth0IdString.split('|'); - const query = { - where: { - provider: provider, - externalId: socialExtId - }, - include: 'user' - }; - // get the email from the auth0 (its expected from social providers) - const email = ensureLowerCaseEmail(profile); - - if (!isEmail('' + email)) { - throw wrapHandledError( - new Error('invalid or empty email received from auth0'), - { - message: dedent` - ${provider} did not return a valid email address. - Please try again with a different account that has an - email associated with it your update your settings on ${provider}, for us to be able to retrieve your email. - `, - type: 'info', - redirectTo: '/' - } - ); - } - - if (provider === 'email') { - return User.findOne$({ where: { email } }) - .flatMap(user => { - return user - ? Observable.of(user) - : User.create$({ email }).toPromise(); - }) - .flatMap(user => { - if (!user) { - throw wrapHandledError( - new Error('could not find or create a user'), - { - message: dedent` - We could not find or create a user with that email address. - `, - type: 'info', - redirectTo: '/' - } - ); - } - const createToken = observeQuery(AccessToken, 'create', { - userId: user.id, - created: new Date(), - ttl: user.constructor.settings.ttl - }); - const updateUserPromise = new Promise((resolve, reject) => - user.updateAttributes( - { - emailVerified: true, - emailAuthLinkTTL: null, - emailVerifyTTL: null - }, - err => { - if (err) { - return reject(err); - } - return resolve(); - } - ) - ); - return Observable.combineLatest( - Observable.of(user), - createToken, - Observable.fromPromise(updateUserPromise), - (user, token) => ({ user, token }) - ); - }) - .subscribe(({ user, token }) => cb(null, user, null, token), cb); - } else { - return UserIdent.findOne$(query) - .flatMap(identity => { - return identity - ? Observable.of(identity.user()) - : User.findOne$({ where: { email } }).flatMap(user => { - return user - ? Observable.of(user) - : User.create$({ email }).toPromise(); - }); - }) - .flatMap(user => { - const createToken = observeQuery(AccessToken, 'create', { - userId: user.id, - created: new Date(), - ttl: user.constructor.settings.ttl - }); - const updateUser = new Promise((resolve, reject) => - user.updateAttributes( - { - email: email, - emailVerified: true, - emailAuthLinkTTL: null, - emailVerifyTTL: null - }, - err => { - if (err) { - return reject(err); - } - return resolve(); - } - ) - ); - return Observable.combineLatest( - Observable.of(user), - createToken, - Observable.fromPromise(updateUser), - (user, token) => ({ user, token }) - ); - }) - .subscribe(({ user, token }) => cb(null, user, null, token), cb); - } - }; -} diff --git a/api-server/src/common/models/User-Identity.json b/api-server/src/common/models/User-Identity.json deleted file mode 100644 index 3288b92b810..00000000000 --- a/api-server/src/common/models/User-Identity.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "userIdentity", - "plural": "userIdentities", - "base": "UserIdentity", - "properties": {}, - "validations": [], - "relations": { - "user": { - "type": "belongsTo", - "model": "user", - "foreignKey": "userId" - } - }, - "acls": [ - { - "accessType": "*", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "DENY" - } - ], - "methods": {} -} diff --git a/api-server/src/common/models/User-Identity.test.js b/api-server/src/common/models/User-Identity.test.js deleted file mode 100644 index 7c5b66a0c42..00000000000 --- a/api-server/src/common/models/User-Identity.test.js +++ /dev/null @@ -1,29 +0,0 @@ -import { ensureLowerCaseEmail } from './User-Identity'; - -test('returns lowercase email when one exists', () => { - const profile = { - id: 2, - emails: [{ value: 'Example@Mail.com', name: 'John Doe' }] - }; - expect(ensureLowerCaseEmail(profile)).toBe('example@mail.com'); -}); - -test('returns empty string when value is undefined', () => { - const profile = { - id: 4, - emails: [] - }; - expect(ensureLowerCaseEmail(profile)).toBe(''); -}); - -test('returns empty string when emails is undefined', () => { - const profile = { - id: 5 - }; - expect(ensureLowerCaseEmail(profile)).toBe(''); -}); - -test('returns empty string when profile is undefined', () => { - let profile; - expect(ensureLowerCaseEmail(profile)).toBe(''); -}); diff --git a/api-server/src/common/models/block.js b/api-server/src/common/models/block.js deleted file mode 100644 index f168fa5da0f..00000000000 --- a/api-server/src/common/models/block.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Observable } from 'rx'; - -export default function initializeBlock(Block) { - Block.on('dataSourceAttached', () => { - Block.findOne$ = Observable.fromNodeCallback(Block.findOne, Block); - Block.findById$ = Observable.fromNodeCallback(Block.findById, Block); - Block.find$ = Observable.fromNodeCallback(Block.find, Block); - }); -} diff --git a/api-server/src/common/models/block.json b/api-server/src/common/models/block.json deleted file mode 100644 index 94e94c4a05a..00000000000 --- a/api-server/src/common/models/block.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "name": "block", - "base": "PersistedModel", - "idInjection": true, - "options": { - "validateUpsert": true - }, - "properties": { - "superBlock": { - "type": "string", - "required": true, - "description": "The super block that this block belongs to" - }, - "order": { - "type": "number", - "required": true, - "description": "The order in which this block appears" - }, - "name": { - "type": "string", - "required": true, - "description": "The name of this block derived from the title, suitable for regex search" - }, - "superOrder": { - "type": "number", - "required": true - }, - "dashedName": { - "type": "string", - "required": true, - "description": "Generated from the title to be URL friendly" - }, - "title": { - "type": "string", - "required": true, - "description": "The title of this block, suitable for display" - }, - "time": { - "type": "string", - "required": false - } - }, - "validations": [], - "relations": { - "challenges": { - "type": "hasMany", - "model": "challenge", - "foreignKey": "blockId" - } - }, - "acls": [], - "methods": {} -} diff --git a/api-server/src/common/models/challenge.json b/api-server/src/common/models/challenge.json deleted file mode 100644 index 753d4adfcea..00000000000 --- a/api-server/src/common/models/challenge.json +++ /dev/null @@ -1,135 +0,0 @@ -{ - "name": "challenge", - "base": "PersistedModel", - "idInjection": true, - "trackChanges": false, - "properties": { - "id": { - "type": "string", - "id": true - }, - "name": { - "type": "string", - "index": { - "mongodb": { - "unique": true, - "background": true - } - } - }, - "title": { - "type": "string" - }, - "order": { - "type": "number" - }, - "suborder": { - "type": "number" - }, - "checksum": { - "type": "number" - }, - "isComingSoon": { - "type": "boolean", - "description": "Challenge shows in production, but is unreachable and disabled. Is reachable in beta/dev only if isBeta flag is set" - }, - "dashedName": { - "type": "string" - }, - "superBlock": { - "type": "string", - "description": "Used for ordering challenge blocks in map" - }, - "superOrder": { - "type": "number", - "description": "Used to determine super block order, set by prepending two digit number to super block folder name" - }, - "block": { - "type": "string" - }, - "difficulty": { - "type": "string" - }, - "description": { - "type": "string" - }, - "tests": { - "type": "array" - }, - "head": { - "type": "string", - "description": "Appended to user code", - "default": "" - }, - "tail": { - "type": "string", - "description": "Prepended to user code", - "default": "" - }, - "helpRoom": { - "type": "string", - "description": "Gitter help chatroom this challenge belongs too. Must be PascalCase", - "default": "Help" - }, - "fileName": { - "type": "string", - "description": "Filename challenge comes from. Used in dev mode" - }, - "challengeSeed": { - "type": "array" - }, - "challengeType": { - "type": "number" - }, - "solutions": { - "type": "array", - "default": [] - }, - "guideUrl": { - "type": "string", - "description": "Used to link to an article in the FCC guide" - }, - "required": { - "type": [ - { - "type": { - "link": { - "type": "string", - "description": "Used for css files" - }, - "src": { - "type": "string", - "description": "Used for script files" - }, - "crossDomain": { - "type": "boolean", - "description": "Files coming from freeCodeCamp must mark this true" - } - } - } - ], - "default": [] - }, - "template": { - "type": "string", - "description": "A template to render the compiled challenge source into. This template uses template literal delimiter, i.e. ${ foo }" - } - }, - "validations": [], - "relations": {}, - "acls": [ - { - "accessType": "*", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "DENY" - }, - { - "accessType": "READ", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "ALLOW" - } - ], - "methods": {} -} diff --git a/api-server/src/common/models/user.js b/api-server/src/common/models/user.js deleted file mode 100644 index 56e67ab78ad..00000000000 --- a/api-server/src/common/models/user.js +++ /dev/null @@ -1,937 +0,0 @@ -/** - * - * Any ref to fixCompletedChallengesItem should be removed post - * a db migration to fix all completedChallenges - * - */ - -import debugFactory from 'debug'; -import dedent from 'dedent'; -import _ from 'lodash'; -import moment from 'moment'; -import { customAlphabet } from 'nanoid'; -import { Observable } from 'rx'; -import uuid from 'uuid/v4'; -import { isEmail } from 'validator'; - -import { isProfane } from 'no-profanity'; -import { blocklistedUsernames } from '../../../../shared/config/constants'; - -import { wrapHandledError } from '../../server/utils/create-handled-error.js'; -import { - setAccessTokenToResponse, - removeCookies -} from '../../server/utils/getSetAccessToken'; -import { saveUser, observeMethod } from '../../server/utils/rx.js'; -import { getEmailSender } from '../../server/utils/url-utils'; -import { - fixCompletedChallengeItem, - getEncodedEmail, - getWaitMessage, - renderEmailChangeEmail, - renderSignUpEmail, - renderSignInEmail -} from '../utils'; - -const log = debugFactory('fcc:models:user'); -const BROWNIEPOINTS_TIMEOUT = [1, 'hour']; -const nanoidCharSet = - '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; -const nanoid = customAlphabet(nanoidCharSet, 21); - -const createEmailError = redirectTo => - wrapHandledError(new Error('email format is invalid'), { - type: 'info', - message: 'Please check to make sure the email is a valid email address.', - redirectTo - }); - -function destroyAll(id, Model) { - return Observable.fromNodeCallback(Model.destroyAll, Model)({ userId: id }); -} - -export function ensureLowerCaseString(maybeString) { - return (maybeString && maybeString.toLowerCase()) || ''; -} - -function buildCompletedChallengesUpdate(completedChallenges, project) { - const key = Object.keys(project)[0]; - const solutions = project[key]; - const solutionKeys = Object.keys(solutions); - const currentCompletedChallenges = [ - ...completedChallenges.map(fixCompletedChallengeItem) - ]; - const currentCompletedProjects = currentCompletedChallenges.filter(({ id }) => - solutionKeys.includes(id) - ); - const now = Date.now(); - const update = solutionKeys.reduce((update, currentId) => { - const indexOfCurrentId = _.findIndex(update, ({ id }) => id === currentId); - const isCurrentlyCompleted = indexOfCurrentId !== -1; - if (isCurrentlyCompleted) { - update[indexOfCurrentId] = { - ..._.find(update, ({ id }) => id === currentId), - solution: solutions[currentId] - }; - } - if (!isCurrentlyCompleted) { - return [ - ...update, - { - id: currentId, - solution: solutions[currentId], - challengeType: 3, - completedDate: now - } - ]; - } - return update; - }, currentCompletedProjects); - const updatedExisting = _.uniqBy( - [...update, ...currentCompletedChallenges], - 'id' - ); - return { - updated: updatedExisting, - isNewCompletionCount: updatedExisting.length - completedChallenges.length - }; -} - -function isTheSame(val1, val2) { - return val1 === val2; -} - -function getAboutProfile({ - username, - usernameDisplay, - githubProfile: github, - progressTimestamps = [], - bio -}) { - return { - username: usernameDisplay || username, - github, - browniePoints: progressTimestamps.length, - bio - }; -} - -function nextTick(fn) { - return process.nextTick(fn); -} - -const getRandomNumber = () => Math.random(); - -function populateRequiredFields(user) { - user.usernameDisplay = user.username.trim(); - user.username = user.usernameDisplay.toLowerCase(); - user.email = - typeof user.email === 'string' - ? user.email.trim().toLowerCase() - : user.email; - - if (!user.progressTimestamps) { - user.progressTimestamps = []; - } - - if (user.progressTimestamps.length === 0) { - user.progressTimestamps.push(Date.now()); - } - - if (!user.externalId) { - user.externalId = uuid(); - } - - if (!user.unsubscribeId) { - user.unsubscribeId = nanoid(); - } - return; -} - -export default function initializeUser(User) { - // set salt factor for passwords - User.settings.saltWorkFactor = 5; - // set user.rand to random number - User.definition.rawProperties.rand.default = getRandomNumber; - User.definition.properties.rand.default = getRandomNumber; - // increase user accessToken ttl to 900 days - User.settings.ttl = 900 * 24 * 60 * 60 * 1000; - // Sets ttl to 900 days for mobile login created access tokens - User.settings.maxTTL = 900 * 24 * 60 * 60 * 1000; - - // username should not be in blocklist - User.validatesExclusionOf('username', { - in: blocklistedUsernames, - message: 'is not available' - }); - - // username should be unique - User.validatesUniquenessOf('username'); - User.settings.emailVerificationRequired = false; - - User.on('dataSourceAttached', () => { - User.findOne$ = Observable.fromNodeCallback(User.findOne, User); - User.count$ = Observable.fromNodeCallback(User.count, User); - User.create$ = Observable.fromNodeCallback(User.create.bind(User)); - User.prototype.createAccessToken$ = Observable.fromNodeCallback( - User.prototype.createAccessToken - ); - }); - - User.observe('before save', function (ctx) { - const beforeCreate = Observable.of(ctx) - .filter(({ isNewInstance }) => isNewInstance) - // User.create - .map(({ instance }) => instance) - .flatMap(user => { - // note(berks): we now require all new users to supply an email - // this was not always the case - if (typeof user.email !== 'string' || !isEmail(user.email)) { - throw createEmailError(); - } - // assign random username to new users - user.username = 'fcc' + uuid(); - populateRequiredFields(user); - return Observable.fromPromise(User.doesExist(null, user.email)).do( - exists => { - if (exists) { - throw wrapHandledError(new Error('user already exists'), { - redirectTo: `${process.env.API_LOCATION}/signin`, - message: dedent` - The ${user.email} email address is already associated with an account. - Try signing in with it here instead. - ` - }); - } - } - ); - }) - .ignoreElements(); - - const updateOrSave = Observable.of(ctx) - // not new - .filter(({ isNewInstance }) => !isNewInstance) - .map(({ instance }) => instance) - // is update or save user - .filter(Boolean) - .do(user => { - // Some old accounts will not have emails associated with them - // we verify only if the email field is populated - if (user.email && !isEmail(user.email)) { - throw createEmailError(); - } - populateRequiredFields(user); - }) - .ignoreElements(); - return Observable.merge(beforeCreate, updateOrSave).toPromise(); - }); - - // remove lingering user identities before deleting user - User.observe('before delete', function (ctx, next) { - const UserIdentity = User.app.models.UserIdentity; - const UserCredential = User.app.models.UserCredential; - log('removing user', ctx.where); - var id = ctx.where && ctx.where.id ? ctx.where.id : null; - if (!id) { - return next(); - } - return Observable.combineLatest( - destroyAll(id, UserIdentity), - destroyAll(id, UserCredential), - function (identData, credData) { - return { - identData: identData, - credData: credData - }; - } - ).subscribe( - function (data) { - log('deleted', data); - }, - function (err) { - log('error deleting user %s stuff', id, err); - next(err); - }, - function () { - log('user stuff deleted for user %s', id); - next(); - } - ); - }); - - log('setting up user hooks'); - // overwrite lb confirm - User.confirm = function (uid, token, redirectTo) { - return this.findById(uid).then(user => { - if (!user) { - throw wrapHandledError(new Error(`User not found: ${uid}`), { - // standard oops - type: 'info', - redirectTo - }); - } - if (user.verificationToken !== token) { - throw wrapHandledError(new Error(`Invalid token: ${token}`), { - type: 'info', - message: dedent` - Looks like you have clicked an invalid link. - Please sign in and request a fresh one. - `, - redirectTo - }); - } - return new Promise((resolve, reject) => - user.updateAttributes( - { - email: user.newEmail, - emailVerified: true, - emailVerifyTTL: null, - newEmail: null, - verificationToken: null - }, - err => { - if (err) { - return reject(err); - } - return resolve(); - } - ) - ); - }); - }; - - User.prototype.loginByRequest = function loginByRequest(req, res) { - const { - query: { emailChange } - } = req; - const createToken = this.createAccessToken$().do(accessToken => { - if (accessToken && accessToken.id) { - setAccessTokenToResponse({ accessToken }, req, res); - } - }); - let data = { - emailVerified: true, - emailAuthLinkTTL: null, - emailVerifyTTL: null - }; - if (emailChange && this.newEmail) { - data = { - ...data, - email: this.newEmail, - newEmail: null - }; - } - const updateUser = new Promise((resolve, reject) => - this.updateAttributes(data, err => { - if (err) { - return reject(err); - } - return resolve(); - }) - ); - return Observable.combineLatest( - createToken, - Observable.fromPromise(updateUser), - req.logIn(this), - accessToken => accessToken - ); - }; - - User.prototype.mobileLoginByRequest = function mobileLoginByRequest( - req, - res - ) { - return new Promise((resolve, reject) => - this.createAccessToken({}, (err, accessToken) => { - if (err) { - return reject(err); - } - setAccessTokenToResponse({ accessToken }, req, res); - return resolve(accessToken); - }) - ); - }; - - User.afterRemote('logout', function ({ req, res }, result, next) { - removeCookies(req, res); - next(); - }); - - User.doesExist = function doesExist(username, email) { - if (!username && (!email || !isEmail(email))) { - return Promise.resolve(false); - } - log('check if username is available'); - // check to see if username is on blocklist - - if ( - username && - (blocklistedUsernames.includes(username) || isProfane(username)) - ) { - return Promise.resolve(true); - } - - var where = {}; - if (username) { - where.username = username.toLowerCase(); - } else { - where.email = email ? email.toLowerCase() : email; - } - log('where', where); - return User.count(where).then(count => count > 0); - }; - - User.about = function about(username, cb) { - if (!username) { - // Zalgo!! - return nextTick(() => { - cb(null, {}); - }); - } - return User.findOne({ where: { username } }, (err, user) => { - if (err) { - return cb(err); - } - if (!user || user.username !== username) { - return cb(null, {}); - } - const aboutUser = getAboutProfile(user); - return cb(null, aboutUser); - }); - }; - - User.remoteMethod('about', { - description: 'get public info about user', - accepts: [ - { - arg: 'username', - type: 'string' - } - ], - returns: [ - { - arg: 'about', - type: 'object' - } - ], - http: { - path: '/about', - verb: 'get' - } - }); - - User.prototype.createAuthToken = function createAuthToken({ ttl } = {}) { - return Observable.fromNodeCallback( - this.authTokens.create.bind(this.authTokens) - )({ ttl }); - }; - - User.prototype.createDonation = function createDonation(donation = {}) { - return Observable.fromNodeCallback( - this.donations.create.bind(this.donations) - )(donation).do(() => - this.updateAttributes({ - isDonating: true, - donationEmails: [...(this.donationEmails || []), donation.email] - }) - ); - }; - - function requestCompletedChallenges() { - return this.getCompletedChallenges$(); - } - - User.prototype.requestCompletedChallenges = requestCompletedChallenges; - - function requestAuthEmail(isSignUp, newEmail) { - return Observable.defer(() => { - const messageOrNull = getWaitMessage(this.emailAuthLinkTTL); - if (messageOrNull) { - throw wrapHandledError(new Error('request is throttled'), { - type: 'info', - message: messageOrNull - }); - } - - // create a temporary access token with ttl for 15 minutes - return this.createAuthToken({ ttl: 15 * 60 * 1000 }); - }) - .flatMap(token => { - let renderAuthEmail = renderSignInEmail; - let subject = 'Your sign in link for freeCodeCamp.org'; - if (isSignUp) { - renderAuthEmail = renderSignUpEmail; - subject = 'Your sign in link for your new freeCodeCamp.org account'; - } - if (newEmail) { - renderAuthEmail = renderEmailChangeEmail; - subject = dedent` - Please confirm your updated email address for freeCodeCamp.org - `; - } - const { id: loginToken, created: emailAuthLinkTTL } = token; - const loginEmail = getEncodedEmail(newEmail ? newEmail : null); - const host = process.env.API_LOCATION; - const mailOptions = { - type: 'email', - to: newEmail ? newEmail : this.email, - from: getEmailSender(), - subject, - text: renderAuthEmail({ - host, - loginEmail, - loginToken, - emailChange: !!newEmail - }) - }; - const userUpdate = new Promise((resolve, reject) => - this.updateAttributes({ emailAuthLinkTTL }, err => { - if (err) { - return reject(err); - } - return resolve(); - }) - ); - return Observable.forkJoin( - User.email.send$(mailOptions), - Observable.fromPromise(userUpdate) - ); - }) - .map({ - type: 'info', - message: dedent`Check your email and click the link we sent you to confirm your new email address.` - }); - } - - User.prototype.requestAuthEmail = requestAuthEmail; - - /** - * @param {String} requestedEmail - */ - function requestUpdateEmail(requestedEmail) { - const newEmail = ensureLowerCaseString(requestedEmail); - const currentEmail = ensureLowerCaseString(this.email); - const isOwnEmail = isTheSame(newEmail, currentEmail); - const isResendUpdateToSameEmail = isTheSame( - newEmail, - ensureLowerCaseString(this.newEmail) - ); - const isLinkSentWithinLimit = getWaitMessage(this.emailVerifyTTL); - const isVerifiedEmail = this.emailVerified; - - if (isOwnEmail && isVerifiedEmail) { - // email is already associated and verified with this account - throw wrapHandledError(new Error('email is already verified'), { - type: 'info', - message: ` - ${newEmail} is already associated with this account. - You can update a new email address instead.` - }); - } - if (isResendUpdateToSameEmail && isLinkSentWithinLimit) { - // trying to update with the same newEmail and - // confirmation email is still valid - throw wrapHandledError(new Error(), { - type: 'info', - message: dedent` - We have already sent an email confirmation request to ${newEmail}. - ${isLinkSentWithinLimit}` - }); - } - if (!isEmail('' + newEmail)) { - throw createEmailError(); - } - - // newEmail is not associated with this user, and - // this attempt to change email is the first or - // previous attempts have expired - if ( - !isOwnEmail || - (isOwnEmail && !isVerifiedEmail) || - (isResendUpdateToSameEmail && !isLinkSentWithinLimit) - ) { - const updateConfig = { - newEmail, - emailVerified: false, - emailVerifyTTL: new Date() - }; - - // defer prevents the promise from firing prematurely (before subscribe) - return Observable.defer(() => User.doesExist(null, newEmail)) - .do(exists => { - if (exists && !isOwnEmail) { - // newEmail is not associated with this account, - // but is associated with different account - throw wrapHandledError(new Error('email already in use'), { - type: 'info', - message: `${newEmail} is already associated with another account.` - }); - } - }) - .flatMap(() => { - const updatePromise = new Promise((resolve, reject) => - this.updateAttributes(updateConfig, err => { - if (err) { - return reject(err); - } - return resolve(); - }) - ); - return Observable.forkJoin( - Observable.fromPromise(updatePromise), - this.requestAuthEmail(false, newEmail), - (_, message) => message - ); - }); - } else { - return 'Something unexpected happened while updating your email.'; - } - } - - User.prototype.requestUpdateEmail = requestUpdateEmail; - - User.prototype.requestUpdateFlags = async function requestUpdateFlags( - values - ) { - const flagsToCheck = Object.keys(values); - const valuesToCheck = _.pick({ ...this }, flagsToCheck); - const flagsToUpdate = flagsToCheck.filter( - flag => !isTheSame(values[flag], valuesToCheck[flag]) - ); - if (!flagsToUpdate.length) { - return Observable.of( - dedent` - No property in - ${JSON.stringify(flagsToCheck, null, 2)} - will introduce a change in this user. - ` - ).map(() => dedent`Your settings have not been updated.`); - } - const userUpdateData = flagsToUpdate.reduce((data, currentFlag) => { - data[currentFlag] = values[currentFlag]; - return data; - }, {}); - log(userUpdateData); - const userUpdate = new Promise((resolve, reject) => - this.updateAttributes(userUpdateData, err => { - if (err) { - return reject(err); - } - return resolve(); - }) - ); - return Observable.fromPromise(userUpdate).map( - () => dedent` - We have successfully updated your account. - ` - ); - }; - - User.prototype.updateMyPortfolio = function updateMyPortfolio( - portfolioItem, - deleteRequest - ) { - const currentPortfolio = this.portfolio.slice(0); - const pIndex = _.findIndex( - currentPortfolio, - p => p.id === portfolioItem.id - ); - let updatedPortfolio = []; - if (deleteRequest) { - updatedPortfolio = currentPortfolio.filter( - p => p.id !== portfolioItem.id - ); - } else if (pIndex === -1) { - updatedPortfolio = currentPortfolio.concat([portfolioItem]); - } else { - updatedPortfolio = [...currentPortfolio]; - updatedPortfolio[pIndex] = { ...portfolioItem }; - } - const userUpdate = new Promise((resolve, reject) => - this.updateAttribute('portfolio', updatedPortfolio, err => { - if (err) { - return reject(err); - } - return resolve(); - }) - ); - return Observable.fromPromise(userUpdate).map( - () => dedent` - Your portfolio has been updated. - ` - ); - }; - - User.prototype.updateMyProjects = function updateMyProjects(project) { - const updateData = { $set: {} }; - return this.getCompletedChallenges$() - .flatMap(() => { - const { updated, isNewCompletionCount } = - buildCompletedChallengesUpdate(this.completedChallenges, project); - updateData.$set.completedChallenges = updated; - if (isNewCompletionCount) { - let points = []; - // give points a length of isNewCompletionCount - points[isNewCompletionCount - 1] = true; - updateData.$push = {}; - updateData.$push.progressTimestamps = { - $each: points.map(() => Date.now()) - }; - } - const updatePromise = new Promise((resolve, reject) => - this.updateAttributes(updateData, err => { - if (err) { - return reject(err); - } - return resolve(); - }) - ); - return Observable.fromPromise(updatePromise); - }) - .map( - () => dedent` - Your projects have been updated. - ` - ); - }; - - User.prototype.updateMyProfileUI = function updateMyProfileUI(profileUI) { - const newProfileUI = { - ...this.profileUI, - ...profileUI - }; - const profileUIUpdate = new Promise((resolve, reject) => - this.updateAttribute('profileUI', newProfileUI, err => { - if (err) { - return reject(err); - } - return resolve(); - }) - ); - return Observable.fromPromise(profileUIUpdate).map( - () => dedent` - Your privacy settings have been updated. - ` - ); - }; - - User.giveBrowniePoints = function giveBrowniePoints( - receiver, - giver, - data = {}, - dev = false, - cb - ) { - const findUser = observeMethod(User, 'findOne'); - if (!receiver) { - return nextTick(() => { - cb(new TypeError(`receiver should be a string but got ${receiver}`)); - }); - } - if (!giver) { - return nextTick(() => { - cb(new TypeError(`giver should be a string but got ${giver}`)); - }); - } - let temp = moment(); - const browniePoints = temp.subtract - .apply(temp, BROWNIEPOINTS_TIMEOUT) - .valueOf(); - const user$ = findUser({ where: { username: receiver } }); - - return ( - user$ - .tapOnNext(user => { - if (!user) { - throw new Error(`could not find receiver for ${receiver}`); - } - }) - .flatMap(({ progressTimestamps = [] }) => { - return Observable.from(progressTimestamps); - }) - // filter out non objects - .filter(timestamp => !!timestamp || typeof timestamp === 'object') - // filter out timestamps older than one hour - .filter(({ timestamp = 0 }) => { - return timestamp >= browniePoints; - }) - // filter out brownie points given by giver - .filter(browniePoint => { - return browniePoint.giver === giver; - }) - // no results means this is the first brownie point given by giver - // so return -1 to indicate receiver should receive point - .first({ defaultValue: -1 }) - .flatMap(browniePointsFromGiver => { - if (browniePointsFromGiver === -1) { - return user$.flatMap(user => { - user.progressTimestamps.push({ - giver, - timestamp: Date.now(), - ...data - }); - return saveUser(user); - }); - } - return Observable.throw( - new Error(`${giver} already gave ${receiver} points`) - ); - }) - .subscribe( - user => { - return cb( - null, - getAboutProfile(user), - dev ? { giver, receiver, data } : null - ); - }, - e => cb(e, null, dev ? { giver, receiver, data } : null), - () => { - log('brownie points assigned completed'); - } - ) - ); - }; - - User.remoteMethod('giveBrowniePoints', { - description: 'Give this user brownie points', - accepts: [ - { - arg: 'receiver', - type: 'string', - required: true - }, - { - arg: 'giver', - type: 'string', - required: true - }, - { - arg: 'data', - type: 'object' - }, - { - arg: 'debug', - type: 'boolean' - } - ], - returns: [ - { - arg: 'about', - type: 'object' - }, - { - arg: 'debug', - type: 'object' - } - ], - http: { - path: '/give-brownie-points', - verb: 'POST' - } - }); - - User.prototype.getPoints$ = function getPoints$() { - if ( - Array.isArray(this.progressTimestamps) && - this.progressTimestamps.length - ) { - return Observable.of(this.progressTimestamps); - } - const id = this.getId(); - const filter = { - where: { id }, - fields: { progressTimestamps: true } - }; - return this.constructor.findOne$(filter).map(user => { - this.progressTimestamps = user.progressTimestamps; - return user.progressTimestamps; - }); - }; - User.prototype.getCompletedChallenges$ = function getCompletedChallenges$() { - if ( - Array.isArray(this.completedChallenges) && - this.completedChallenges.length - ) { - return Observable.of(this.completedChallenges); - } - const id = this.getId(); - const filter = { - where: { id }, - fields: { completedChallenges: true } - }; - return this.constructor.findOne$(filter).map(user => { - this.completedChallenges = user.completedChallenges; - return user.completedChallenges; - }); - }; - User.prototype.getSavedChallenges$ = function getSavedChallenges$() { - if (Array.isArray(this.savedChallenges) && this.savedChallenges.length) { - return Observable.of(this.savedChallenges); - } - const id = this.getId(); - const filter = { - where: { id }, - fields: { savedChallenges: true } - }; - return this.constructor.findOne$(filter).map(user => { - this.savedChallenges = user.savedChallenges; - return user.savedChallenges; - }); - }; - - User.prototype.getPartiallyCompletedChallenges$ = - function getPartiallyCompletedChallenges$() { - if ( - Array.isArray(this.partiallyCompletedChallenges) && - this.partiallyCompletedChallenges.length - ) { - return Observable.of(this.partiallyCompletedChallenges); - } - const id = this.getId(); - const filter = { - where: { id }, - fields: { partiallyCompletedChallenges: true } - }; - return this.constructor.findOne$(filter).map(user => { - this.partiallyCompletedChallenges = user.partiallyCompletedChallenges; - return user.partiallyCompletedChallenges; - }); - }; - - User.prototype.getCompletedExams$ = function getCompletedExams$() { - if (Array.isArray(this.completedExams) && this.completedExams.length) { - return Observable.of(this.completedExams); - } - const id = this.getId(); - const filter = { - where: { id }, - fields: { completedExams: true } - }; - return this.constructor.findOne$(filter).map(user => { - this.completedExams = user.completedExams; - return user.completedExams; - }); - }; - - User.getMessages = messages => Promise.resolve(messages); - - User.remoteMethod('getMessages', { - http: { - verb: 'get', - path: '/get-messages' - }, - accepts: { - arg: 'messages', - type: 'object', - http: ctx => ctx.req.flash() - }, - returns: [ - { - arg: 'messages', - type: 'object', - root: true - } - ] - }); -} diff --git a/api-server/src/common/models/user.json b/api-server/src/common/models/user.json deleted file mode 100644 index 28c76668657..00000000000 --- a/api-server/src/common/models/user.json +++ /dev/null @@ -1,520 +0,0 @@ -{ - "name": "user", - "base": "User", - "strict": "filter", - "idInjection": true, - "emailVerificationRequired": false, - "trackChanges": false, - "properties": { - "email": { - "type": "string", - "index": { - "mongodb": { - "unique": true, - "background": true, - "sparse": true - } - } - }, - "newEmail": { - "type": "string" - }, - "emailVerifyTTL": { - "type": "date" - }, - "emailVerified": { - "type": "boolean", - "default": false - }, - "emailAuthLinkTTL": { - "type": "date" - }, - "externalId": { - "type": "string", - "description": "A uuid/v4 used to identify user accounts" - }, - "unsubscribeId": { - "type": "string", - "description": "An ObjectId used to unsubscribe users from the mailing list(s)" - }, - "password": { - "type": "string", - "description": "No longer used for new accounts" - }, - "progressTimestamps": { - "type": "array", - "default": [] - }, - "isBanned": { - "type": "boolean", - "description": "User is banned from posting to camper news", - "default": false - }, - "isCheater": { - "type": "boolean", - "description": "Users who are confirmed to have broken academic honesty policy are marked as cheaters", - "default": false - }, - "githubProfile": { - "type": "string" - }, - "website": { - "type": "string" - }, - "_csrf": { - "type": "string" - }, - "username": { - "type": "string", - "index": { - "mongodb": { - "unique": true, - "background": true - } - }, - "require": true - }, - "usernameDisplay": { - "type": "string" - }, - "about": { - "type": "string", - "default": "" - }, - "name": { - "type": "string", - "default": "" - }, - "location": { - "type": "string", - "default": "" - }, - "picture": { - "type": "string", - "default": "" - }, - "linkedin": { - "type": "string" - }, - "codepen": { - "type": "string" - }, - "twitter": { - "type": "string" - }, - "acceptedPrivacyTerms": { - "type": "boolean", - "default": false - }, - "sendQuincyEmail": { - "type": "boolean", - "default": false - }, - "isClassroomAccount": { - "type": "boolean", - "default": false - }, - "currentChallengeId": { - "type": "string", - "description": "The challenge last visited by the user", - "default": "" - }, - "isHonest": { - "type": "boolean", - "description": "Camper has signed academic honesty policy", - "default": false - }, - "needsModeration": { - "type": "boolean", - "description": "Camper has challenges needing to be moderated", - "default": false, - "index": { - "mongodb": { - "unique": true, - "background": true - } - } - }, - "isFrontEndCert": { - "type": "boolean", - "description": "Camper is front end certified", - "default": false - }, - "isDataVisCert": { - "type": "boolean", - "description": "Camper is data visualization certified", - "default": false - }, - "isBackEndCert": { - "type": "boolean", - "description": "Campers is back end certified", - "default": false - }, - "isFullStackCert": { - "type": "boolean", - "description": "Campers is full stack certified", - "default": false - }, - "isRespWebDesignCert": { - "type": "boolean", - "description": "Camper is responsive web design certified", - "default": false - }, - "is2018DataVisCert": { - "type": "boolean", - "description": "Camper is data visualization certified (2018)", - "default": false - }, - "isFrontEndLibsCert": { - "type": "boolean", - "description": "Camper is front end libraries certified", - "default": false - }, - "isJsAlgoDataStructCert": { - "type": "boolean", - "description": "Camper is javascript algorithms and data structures certified", - "default": false - }, - "isApisMicroservicesCert": { - "type": "boolean", - "description": "Camper is apis and microservices certified", - "default": false - }, - "isInfosecQaCert": { - "type": "boolean", - "description": "Camper is information security and quality assurance certified", - "default": false - }, - "isQaCertV7": { - "type": "boolean", - "description": "Camper is quality assurance certified", - "default": false - }, - "isInfosecCertV7": { - "type": "boolean", - "description": "Camper is information security certified", - "default": false - }, - "is2018FullStackCert": { - "type": "boolean", - "description": "Camper is full stack certified (2018)", - "default": false - }, - "isSciCompPyCertV7": { - "type": "boolean", - "description": "Camper is scientific computing with Python certified", - "default": false - }, - "isDataAnalysisPyCertV7": { - "type": "boolean", - "description": "Camper is data analysis with Python certified", - "default": false - }, - "isMachineLearningPyCertV7": { - "type": "boolean", - "description": "Camper is machine learning with Python certified", - "default": false - }, - "isRelationalDatabaseCertV8": { - "type": "boolean", - "description": "Camper is relational database certified", - "default": false - }, - "isCollegeAlgebraPyCertV8": { - "type": "boolean", - "description": "Camper is college algebra with Python certified", - "default": false - }, - "isFoundationalCSharpCertV8": { - "type": "boolean", - "description": "Camper is foundational C# certified", - "default": false - }, - "isJsAlgoDataStructCertV8": { - "type": "boolean", - "description": "Camper is javascript algorithms and data structures certified (2023)", - "default": false - }, - "completedChallenges": { - "type": [ - { - "completedDate": "number", - "id": "string", - "solution": "string", - "githubLink": "string", - "challengeType": "number", - "isManuallyApproved": "boolean", - "files": { - "type": [ - { - "contents": { - "type": "string", - "default": "" - }, - "ext": { - "type": "string" - }, - "path": { - "type": "string" - }, - "name": { - "type": "string" - }, - "key": { - "type": "string" - } - } - ], - "default": [] - } - } - ], - "default": [] - }, - "partiallyCompletedChallenges": { - "type": [ - { - "completedDate": "number", - "id": "string" - } - ], - "default": [] - }, - "savedChallenges": { - "type": [ - { - "lastSavedDate": "number", - "id": "string", - "challengeType": "number", - "files": { - "type": [ - { - "contents": { - "type": "string", - "default": "" - }, - "ext": { - "type": "string" - }, - "path": { - "type": "string" - }, - "name": { - "type": "string" - }, - "key": { - "type": "string" - } - } - ], - "default": [] - } - } - ], - "default": [] - }, - "completedExams": { - "type": [ - { - "completedDate": "number", - "id": "string", - "challengeType": "number", - "examResults": { - "type": { - "numberOfCorrectAnswers": "number", - "numberOfQuestionsInExam": "number", - "percentCorrect": "number", - "passingPercent": "number", - "passed": "boolean", - "examTimeInSeconds": "number" - } - } - } - ], - "default": [] - }, - "portfolio": { - "type": "array", - "default": [] - }, - "yearsTopContributor": { - "type": "array", - "default": [] - }, - "rand": { - "type": "number", - "index": true - }, - "timezone": { - "type": "string" - }, - "theme": { - "type": "string", - "default": "default" - }, - "keyboardShortcuts": { - "type": "boolean", - "default": false - }, - "profileUI": { - "type": "object", - "default": { - "isLocked": true, - "showAbout": false, - "showCerts": false, - "showDonation": false, - "showHeatMap": false, - "showLocation": false, - "showName": false, - "showPoints": false, - "showPortfolio": false, - "showTimeLine": false - } - }, - "badges": { - "type": { - "coreTeam": { - "type": "array", - "default": [] - } - }, - "default": {} - }, - "donationEmails": { - "type": ["string"] - }, - "isDonating": { - "type": "boolean", - "description": "Does the camper have an active donation", - "default": false - } - }, - "validations": [], - "relations": { - "donations": { - "type": "hasMany", - "foreignKey": "", - "modal": "donation" - }, - "credentials": { - "type": "hasMany", - "model": "userCredential", - "foreignKey": "" - }, - "identities": { - "type": "hasMany", - "model": "userIdentity", - "foreignKey": "" - }, - "pledge": { - "type": "hasOne", - "model": "pledge", - "foreignKey": "" - }, - "authTokens": { - "type": "hasMany", - "model": "AuthToken", - "foreignKey": "userId", - "options": { - "disableInclude": true - } - }, - "articles": { - "type": "hasMany", - "model": "article", - "foreignKey": "externalId" - }, - "userTokens": { - "type": "hasMany", - "model": "UserToken", - "foreignKey": "userId" - }, - "msUsernames": { - "type": "hasMany", - "model": "MsUsername", - "foreignKey": "userId" - }, - "surveys": { - "type": "hasMany", - "model": "Survey", - "foreignKey": "userId" - } - }, - "acls": [ - { - "accessType": "*", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "DENY" - }, - { - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "DENY", - "property": "create" - }, - { - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "DENY", - "property": "login" - }, - { - "accessType": "EXECUTE", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "DENY", - "property": "verify" - }, - { - "accessType": "EXECUTE", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "DENY", - "property": "resetPassword" - }, - { - "accessType": "EXECUTE", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "ALLOW", - "property": "doesExist" - }, - { - "accessType": "EXECUTE", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "ALLOW", - "property": "about" - }, - { - "accessType": "EXECUTE", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "ALLOW", - "property": "getPublicProfile" - }, - { - "accessType": "EXECUTE", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "ALLOW", - "property": "giveBrowniePoints" - }, - { - "accessType": "EXECUTE", - "principalType": "ROLE", - "principalId": "$owner", - "permission": "ALLOW", - "property": "updateTheme" - }, - { - "accessType": "EXECUTE", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "ALLOW", - "property": "getMessages" - } - ], - "methods": {} -} diff --git a/api-server/src/common/utils/auth.js b/api-server/src/common/utils/auth.js deleted file mode 100644 index 666a8e699c1..00000000000 --- a/api-server/src/common/utils/auth.js +++ /dev/null @@ -1,80 +0,0 @@ -import path from 'path'; - -import dedent from 'dedent'; -import loopback from 'loopback'; -import moment from 'moment'; - -export const renderSignUpEmail = loopback.template( - path.join( - __dirname, - '..', - '..', - 'server', - 'views', - 'emails', - 'user-request-sign-up.ejs' - ) -); - -export const renderSignInEmail = loopback.template( - path.join( - __dirname, - '..', - '..', - 'server', - 'views', - 'emails', - 'user-request-sign-in.ejs' - ) -); - -export const renderEmailChangeEmail = loopback.template( - path.join( - __dirname, - '..', - '..', - 'server', - 'views', - 'emails', - 'user-request-update-email.ejs' - ) -); - -export function getWaitPeriod(ttl) { - const fiveMinutesAgo = moment().subtract(5, 'minutes'); - const lastEmailSentAt = moment(new Date(ttl || null)); - const isWaitPeriodOver = ttl - ? lastEmailSentAt.isBefore(fiveMinutesAgo) - : true; - - if (!isWaitPeriodOver) { - const minutesLeft = 5 - (moment().minutes() - lastEmailSentAt.minutes()); - return minutesLeft; - } - - return 0; -} - -export function getWaitMessage(ttl) { - const minutesLeft = getWaitPeriod(ttl); - if (minutesLeft <= 0) { - return null; - } - - const timeToWait = minutesLeft - ? `${minutesLeft} minute${minutesLeft > 1 ? 's' : ''}` - : 'a few seconds'; - - return dedent` - Please wait ${timeToWait} to resend an authentication link. - `; -} - -export function getEncodedEmail(email) { - if (!email) { - return null; - } - return Buffer.from(email).toString('base64'); -} - -export const decodeEmail = email => Buffer.from(email, 'base64').toString(); diff --git a/api-server/src/common/utils/constantStrings.json b/api-server/src/common/utils/constantStrings.json deleted file mode 100644 index 195890ccffa..00000000000 --- a/api-server/src/common/utils/constantStrings.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "aboutUrl": "https://www.freecodecamp.org/about", - "defaultProfileImage": "https://cdn.freecodecamp.org/platform/universal/camper-image-placeholder.png", - "donateUrl": "https://www.freecodecamp.org/donate", - "forumUrl": "https://forum.freecodecamp.org", - "githubUrl": "https://github.com/freecodecamp/freecodecamp", - "RSA": "https://forum.freecodecamp.org/t/the-read-search-ask-methodology-for-getting-unstuck/137307", - "homeURL": "https://www.freecodecamp.org" -} diff --git a/api-server/src/common/utils/empty-protector.js b/api-server/src/common/utils/empty-protector.js deleted file mode 100644 index 517ecba917f..00000000000 --- a/api-server/src/common/utils/empty-protector.js +++ /dev/null @@ -1,13 +0,0 @@ -const emptyProtector = { - blocks: [], - challenges: [] -}; -// protect against malformed map data -// protect(block: { challenges: [], block: [] }|Void) => block|emptyProtector -export default function protect(block) { - // if no block or block has no challenges or blocks - if (!block || !(block.challenges || block.blocks)) { - return emptyProtector; - } - return block; -} diff --git a/api-server/src/common/utils/flash.js b/api-server/src/common/utils/flash.js deleted file mode 100644 index b1ece41452a..00000000000 --- a/api-server/src/common/utils/flash.js +++ /dev/null @@ -1,8 +0,0 @@ -import _ from 'lodash'; - -export const alertTypes = _.keyBy( - ['success', 'info', 'warning', 'danger'], - _.identity -); - -export const normalizeAlertType = alertType => alertTypes[alertType] || 'info'; diff --git a/api-server/src/common/utils/index.js b/api-server/src/common/utils/index.js deleted file mode 100644 index cfc64be9267..00000000000 --- a/api-server/src/common/utils/index.js +++ /dev/null @@ -1,34 +0,0 @@ -import { pick } from 'lodash'; - -export { - getEncodedEmail, - decodeEmail, - getWaitMessage, - getWaitPeriod, - renderEmailChangeEmail, - renderSignUpEmail, - renderSignInEmail -} from './auth'; - -export const fixCompletedChallengeItem = obj => - pick(obj, [ - 'id', - 'completedDate', - 'solution', - 'githubLink', - 'challengeType', - 'files', - 'isManuallyApproved', - 'examResults' - ]); - -export const fixSavedChallengeItem = obj => - pick(obj, ['id', 'lastSavedDate', 'files']); - -export const fixPartiallyCompletedChallengeItem = obj => - pick(obj, ['id', 'completedDate']); - -export const fixCompletedExamItem = obj => - pick(obj, ['id', 'completedDate', 'challengeType', 'examResults']); - -export const fixCompletedSurveyItem = obj => pick(obj, ['title', 'responses']); diff --git a/api-server/src/development-start.js b/api-server/src/development-start.js deleted file mode 100644 index f1317cbbfe9..00000000000 --- a/api-server/src/development-start.js +++ /dev/null @@ -1,21 +0,0 @@ -const path = require('path'); -require('dotenv').config({ path: path.resolve(__dirname, '../../.env') }); - -const createDebugger = require('debug'); -const nodemon = require('nodemon'); -const log = createDebugger('fcc:start:development'); - -nodemon({ - ext: 'js json', - // --silent squashes an ELIFECYCLE error when the server exits - exec: 'pnpm run --silent babel-dev-server', - watch: path.resolve(__dirname, './server'), - spawn: true, - env: { - DEBUG: `fcc*,${process.env.DEBUG}` - } -}); - -nodemon.on('restart', function nodemonRestart(files) { - log('App restarted due to: ', files); -}); diff --git a/api-server/src/production-start.js b/api-server/src/production-start.js deleted file mode 100644 index ac389b26355..00000000000 --- a/api-server/src/production-start.js +++ /dev/null @@ -1,31 +0,0 @@ -// this ensures node understands the future -const createDebugger = require('debug'); -const _ = require('lodash'); - -const log = createDebugger('fcc:server:production-start'); -const startTime = Date.now(); -// force logger to always output -// this may be brittle -log.enabled = true; -// this is where server starts booting up -const app = require('./server'); - -let timeoutHandler; -let killTime = 15; - -const onConnect = _.once(() => { - log('db connected in: %s', Date.now() - startTime); - if (timeoutHandler) { - clearTimeout(timeoutHandler); - } - app.start(); -}); - -timeoutHandler = setTimeout(() => { - const message = `db did not connect after ${killTime}s -- crashing hard`; - // purposely shutdown server - // pm2 should restart this in production - throw new Error(message); -}, killTime * 1000); - -app.dataSources.db.on('connected', onConnect); diff --git a/api-server/src/server/boot/a-extend-built-ins.js b/api-server/src/server/boot/a-extend-built-ins.js deleted file mode 100644 index 3466a9a8135..00000000000 --- a/api-server/src/server/boot/a-extend-built-ins.js +++ /dev/null @@ -1,15 +0,0 @@ -import { Observable } from 'rx'; - -export default function extendEmail(app) { - const { AccessToken, Email } = app.models; - Email.send$ = Observable.fromNodeCallback(Email.send, Email); - AccessToken.findOne$ = Observable.fromNodeCallback( - AccessToken.findOne.bind(AccessToken) - ); - AccessToken.prototype.validate$ = Observable.fromNodeCallback( - AccessToken.prototype.validate - ); - AccessToken.prototype.destroy$ = Observable.fromNodeCallback( - AccessToken.prototype.destroy - ); -} diff --git a/api-server/src/server/boot/a-increase-listeners.js b/api-server/src/server/boot/a-increase-listeners.js deleted file mode 100644 index edcf3eb05de..00000000000 --- a/api-server/src/server/boot/a-increase-listeners.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = function increaseListers(app) { - // increase loopback database ODM max listeners - // this is a EventEmitter method - app.dataSources.db.setMaxListeners(32); -}; diff --git a/api-server/src/server/boot/authentication.js b/api-server/src/server/boot/authentication.js deleted file mode 100644 index c78c3b0fd45..00000000000 --- a/api-server/src/server/boot/authentication.js +++ /dev/null @@ -1,246 +0,0 @@ -import dedent from 'dedent'; -import { check } from 'express-validator'; -import jwt from 'jsonwebtoken'; -import passport from 'passport'; -import fetch from 'node-fetch'; -import { isEmail } from 'validator'; -import { jwtSecret } from '../../../config/secrets'; -import { decodeEmail } from '../../common/utils'; -import { - createPassportCallbackAuthenticator, - devSaveResponseAuthCookies, - devLoginRedirect -} from '../component-passport'; -import { wrapHandledError } from '../utils/create-handled-error.js'; -import { removeCookies } from '../utils/getSetAccessToken'; -import { - ifUserRedirectTo, - ifNoUserRedirectHome, - ifNotMobileRedirect -} from '../utils/middleware'; -import { getRedirectParams } from '../utils/redirection'; -import { createDeleteUserToken } from '../middlewares/user-token'; - -const passwordlessGetValidators = [ - check('email') - .isBase64() - .withMessage('Email should be a base64 encoded string.'), - check('token') - .exists() - .withMessage('Token should exist.') - // based on strongloop/loopback/common/models/access-token.js#L15 - .isLength({ min: 64, max: 64 }) - .withMessage('Token is not the right length.') -]; - -module.exports = function enableAuthentication(app) { - // enable loopback access control authentication. see: - // loopback.io/doc/en/lb2/Authentication-authorization-and-permissions.html - app.enableAuth(); - const ifNotMobile = ifNotMobileRedirect(); - const ifUserRedirect = ifUserRedirectTo(); - const ifNoUserRedirect = ifNoUserRedirectHome(); - const devSaveAuthCookies = devSaveResponseAuthCookies(); - const devLoginSuccessRedirect = devLoginRedirect(); - const api = app.loopback.Router(); - const deleteUserToken = createDeleteUserToken(app); - - // Use a local mock strategy for signing in if we are in dev mode. - // Otherwise we use auth0 login. We use a string for 'true' because values - // set in the env file will always be strings and never boolean. - if (process.env.LOCAL_MOCK_AUTH === 'true') { - api.get( - '/signin', - passport.authenticate('devlogin'), - devSaveAuthCookies, - devLoginSuccessRedirect - ); - } else { - api.get('/signin', ifUserRedirect, (req, res, next) => { - const { returnTo, origin, pathPrefix } = getRedirectParams(req); - const state = jwt.sign({ returnTo, origin, pathPrefix }, jwtSecret); - return passport.authenticate('auth0-login', { state })(req, res, next); - }); - - api.get( - '/auth/auth0/callback', - createPassportCallbackAuthenticator('auth0-login', { provider: 'auth0' }) - ); - } - - api.get('/signout', deleteUserToken, (req, res) => { - const { origin, returnTo } = getRedirectParams(req); - req.logout(); - req.session.destroy(err => { - if (err) { - throw wrapHandledError(new Error('could not destroy session'), { - type: 'info', - message: 'We could not log you out, please try again in a moment.', - redirectTo: origin - }); - } - removeCookies(req, res); - res.redirect(returnTo); - }); - }); - - api.get( - '/confirm-email', - ifNoUserRedirect, - passwordlessGetValidators, - createGetPasswordlessAuth(app) - ); - - api.get('/mobile-login', ifNotMobile, ifUserRedirect, mobileLogin(app)); - - app.use(api); -}; - -const defaultErrorMsg = dedent` - Oops, something is not right, - please request a fresh link to sign in / sign up. - `; - -function createGetPasswordlessAuth(app) { - const { - models: { AuthToken, User } - } = app; - return function getPasswordlessAuth(req, res, next) { - const { - query: { email: encodedEmail, token: authTokenId, emailChange } = {} - } = req; - const { origin } = getRedirectParams(req); - const email = decodeEmail(encodedEmail); - if (!isEmail(email)) { - return next( - wrapHandledError(new TypeError('decoded email is invalid'), { - type: 'info', - message: 'The email encoded in the link is incorrectly formatted', - redirectTo: `${origin}/signin` - }) - ); - } - // first find - return ( - AuthToken.findOne$({ where: { id: authTokenId } }) - .flatMap(authToken => { - if (!authToken) { - throw wrapHandledError( - new Error(`no token found for id: ${authTokenId}`), - { - type: 'info', - message: defaultErrorMsg, - redirectTo: `${origin}/signin` - } - ); - } - // find user then validate and destroy email validation token - // finally return user instance - return User.findOne$({ where: { id: authToken.userId } }).flatMap( - user => { - if (!user) { - throw wrapHandledError( - new Error(`no user found for token: ${authTokenId}`), - { - type: 'info', - message: defaultErrorMsg, - redirectTo: `${origin}/signin` - } - ); - } - if (user.email !== email) { - if (!emailChange || (emailChange && user.newEmail !== email)) { - throw wrapHandledError( - new Error('user email does not match'), - { - type: 'info', - message: defaultErrorMsg, - redirectTo: `${origin}/signin` - } - ); - } - } - return authToken - .validate$() - .map(isValid => { - if (!isValid) { - throw wrapHandledError(new Error('token is invalid'), { - type: 'info', - message: ` - Looks like the link you clicked has expired, - please request a fresh link, to sign in. - `, - redirectTo: `${origin}/signin` - }); - } - return authToken.destroy$(); - }) - .map(() => user); - } - ); - }) - // at this point token has been validated and destroyed - // update user and log them in - .map(user => user.loginByRequest(req, res)) - .do(() => { - if (emailChange) { - req.flash('success', 'flash.email-valid'); - } else { - req.flash('success', 'flash.signin-success'); - } - return res.redirectWithFlash(`${origin}/learn`); - }) - .subscribe(() => {}, next) - ); - }; -} - -function mobileLogin(app) { - const { - models: { User } - } = app; - return async function getPasswordlessAuth(req, res, next) { - try { - const auth0Res = await fetch( - `https://${process.env.AUTH0_DOMAIN}/userinfo`, - { - headers: { Authorization: req.headers.authorization } - } - ); - - if (!auth0Res.ok) { - return next( - wrapHandledError(new Error('Invalid Auth0 token'), { - type: 'danger', - message: 'We could not log you in, please try again in a moment.', - status: auth0Res.status - }) - ); - } - - const { email } = await auth0Res.json(); - - if (typeof email !== 'string' || !isEmail(email)) { - return next( - wrapHandledError(new TypeError('decoded email is invalid'), { - type: 'danger', - message: 'The email is incorrectly formatted', - status: 400 - }) - ); - } - - User.findOne$({ where: { email } }) - .do(async user => { - if (!user) { - user = await User.create({ email }); - } - await user.mobileLoginByRequest(req, res); - res.end(); - }) - .subscribe(() => {}, next); - } catch (err) { - next(err); - } - }; -} diff --git a/api-server/src/server/boot/certificate.js b/api-server/src/server/boot/certificate.js deleted file mode 100644 index b7cd6127795..00000000000 --- a/api-server/src/server/boot/certificate.js +++ /dev/null @@ -1,569 +0,0 @@ -import path from 'path'; -import debug from 'debug'; -import dedent from 'dedent'; -import _ from 'lodash'; -import loopback from 'loopback'; -import { Observable } from 'rx'; -import { isEmail } from 'validator'; -import { - completionHours, - certTypes, - certSlugTypeMap, - certTypeTitleMap, - certTypeIdMap, - certIds, - oldDataVizId, - currentCertifications, - upcomingCertifications, - legacyCertifications, - legacyFullStackCertification -} from '../../../../shared/config/certification-settings'; -import { reportError } from '../middlewares/sentry-error-handler.js'; - -import { deprecatedEndpoint } from '../utils/disabled-endpoints'; -import { getChallenges } from '../utils/get-curriculum'; -import { ifNoUser401 } from '../utils/middleware'; -import { observeQuery } from '../utils/rx'; - -const { - legacyFrontEndChallengeId, - legacyBackEndChallengeId, - legacyDataVisId, - legacyInfosecQaId, - legacyFullStackId, - respWebDesignId, - frontEndDevLibsId, - jsAlgoDataStructId, - dataVis2018Id, - apisMicroservicesId, - qaV7Id, - infosecV7Id, - sciCompPyV7Id, - dataAnalysisPyV7Id, - machineLearningPyV7Id, - relationalDatabaseV8Id, - collegeAlgebraPyV8Id, - foundationalCSharpV8Id, - jsAlgoDataStructV8Id -} = certIds; - -const log = debug('fcc:certification'); - -export default function bootCertificate(app) { - const api = app.loopback.Router(); - // TODO: rather than getting all the challenges, then grabbing the certs, - // consider just getting the certs. - const certTypeIds = createCertTypeIds(getChallenges()); - const showCert = createShowCert(app); - const verifyCert = createVerifyCert(certTypeIds, app); - - api.put('/certificate/verify', ifNoUser401, ifNoCertification404, verifyCert); - api.get('/certificate/showCert/:username/:certSlug', showCert); - api.get('/certificate/verify-can-claim-cert', deprecatedEndpoint); - app.use(api); -} - -export function getFallbackFullStackDate(completedChallenges, completedDate) { - var chalIds = [ - respWebDesignId, - jsAlgoDataStructId, - frontEndDevLibsId, - dataVis2018Id, - apisMicroservicesId, - legacyInfosecQaId - ]; - - const latestCertDate = completedChallenges - .filter(chal => chalIds.includes(chal.id)) - .sort((a, b) => b.completedDate - a.completedDate)[0]?.completedDate; - - return latestCertDate ? latestCertDate : completedDate; -} - -export function ifNoCertification404(req, res, next) { - const { certSlug } = req.body; - if (!certSlug) return res.status(404).end(); - if ( - currentCertifications.includes(certSlug) || - legacyCertifications.includes(certSlug) || - legacyFullStackCertification.includes(certSlug) - ) - return next(); - if ( - process.env.SHOW_UPCOMING_CHANGES === 'true' && - upcomingCertifications.includes(certSlug) - ) { - return next(); - } - res.status(404).end(); -} - -const renderCertifiedEmail = loopback.template( - path.join(__dirname, '..', 'views', 'emails', 'certified.ejs') -); - -function createCertTypeIds(allChallenges) { - return { - // legacy - [certTypes.frontEnd]: getCertById(legacyFrontEndChallengeId, allChallenges), - [certTypes.jsAlgoDataStruct]: getCertById( - jsAlgoDataStructId, - allChallenges - ), - [certTypes.backEnd]: getCertById(legacyBackEndChallengeId, allChallenges), - [certTypes.dataVis]: getCertById(legacyDataVisId, allChallenges), - [certTypes.infosecQa]: getCertById(legacyInfosecQaId, allChallenges), - [certTypes.fullStack]: getCertById(legacyFullStackId, allChallenges), - - // modern - [certTypes.respWebDesign]: getCertById(respWebDesignId, allChallenges), - [certTypes.jsAlgoDataStructV8]: getCertById( - jsAlgoDataStructV8Id, - allChallenges - ), - [certTypes.frontEndDevLibs]: getCertById(frontEndDevLibsId, allChallenges), - [certTypes.dataVis2018]: getCertById(dataVis2018Id, allChallenges), - [certTypes.apisMicroservices]: getCertById( - apisMicroservicesId, - allChallenges - ), - [certTypes.qaV7]: getCertById(qaV7Id, allChallenges), - [certTypes.infosecV7]: getCertById(infosecV7Id, allChallenges), - [certTypes.sciCompPyV7]: getCertById(sciCompPyV7Id, allChallenges), - [certTypes.dataAnalysisPyV7]: getCertById( - dataAnalysisPyV7Id, - allChallenges - ), - [certTypes.machineLearningPyV7]: getCertById( - machineLearningPyV7Id, - allChallenges - ), - [certTypes.relationalDatabaseV8]: getCertById( - relationalDatabaseV8Id, - allChallenges - ), - [certTypes.collegeAlgebraPyV8]: getCertById( - collegeAlgebraPyV8Id, - allChallenges - ), - [certTypes.foundationalCSharpV8]: getCertById( - foundationalCSharpV8Id, - allChallenges - ) - }; -} - -function hasCompletedTests(ids, completedChallenges = []) { - return _.every(ids, ({ id }) => - _.find(completedChallenges, ({ id: completedId }) => completedId === id) - ); -} - -function getCertById(anId, allChallenges) { - return allChallenges - .filter(({ id }) => id === anId) - .map(({ id, tests, name, challengeType }) => ({ - id, - tests, - name, - challengeType - }))[0]; -} - -function sendCertifiedEmail( - { - email = '', - name, - username, - isRespWebDesignCert, - isJsAlgoDataStructCertV8, - isFrontEndLibsCert, - isDataVisCert, - isApisMicroservicesCert, - isQaCertV7, - isInfosecCertV7, - isSciCompPyCertV7, - isDataAnalysisPyCertV7, - isMachineLearningPyCertV7, - isRelationalDatabaseCertV8, - isCollegeAlgebraPyCertV8, - isFoundationalCSharpCertV8 - }, - send$ -) { - if ( - !isEmail(email) || - !isRespWebDesignCert || - !isJsAlgoDataStructCertV8 || - !isFrontEndLibsCert || - !isDataVisCert || - !isApisMicroservicesCert || - !isQaCertV7 || - !isInfosecCertV7 || - !isSciCompPyCertV7 || - !isDataAnalysisPyCertV7 || - !isMachineLearningPyCertV7 || - !isRelationalDatabaseCertV8 || - !isCollegeAlgebraPyCertV8 || - !isFoundationalCSharpCertV8 - ) { - return Observable.just(false); - } - const notifyUser = { - type: 'email', - to: email, - from: 'quincy@freecodecamp.org', - subject: dedent` - Congratulations on completing all of the - freeCodeCamp certifications! - `, - text: renderCertifiedEmail({ - username, - name - }) - }; - return send$(notifyUser).map(() => true); -} - -function getUserIsCertMap(user) { - const { - isRespWebDesignCert = false, - isJsAlgoDataStructCert = false, - isJsAlgoDataStructCertV8 = false, - isFrontEndLibsCert = false, - is2018DataVisCert = false, - isApisMicroservicesCert = false, - isInfosecQaCert = false, - isQaCertV7 = false, - isInfosecCertV7 = false, - isFrontEndCert = false, - isBackEndCert = false, - isDataVisCert = false, - isFullStackCert = false, - isSciCompPyCertV7 = false, - isDataAnalysisPyCertV7 = false, - isMachineLearningPyCertV7 = false, - isRelationalDatabaseCertV8 = false, - isCollegeAlgebraPyCertV8 = false, - isFoundationalCSharpCertV8 = false - } = user; - - return { - isRespWebDesignCert, - isJsAlgoDataStructCert, - isJsAlgoDataStructCertV8, - isFrontEndLibsCert, - is2018DataVisCert, - isApisMicroservicesCert, - isInfosecQaCert, - isQaCertV7, - isInfosecCertV7, - isFrontEndCert, - isBackEndCert, - isDataVisCert, - isFullStackCert, - isSciCompPyCertV7, - isDataAnalysisPyCertV7, - isMachineLearningPyCertV7, - isRelationalDatabaseCertV8, - isCollegeAlgebraPyCertV8, - isFoundationalCSharpCertV8 - }; -} - -function createVerifyCert(certTypeIds, app) { - const { Email } = app.models; - return function verifyCert(req, res, next) { - const { - body: { certSlug }, - user - } = req; - log(certSlug); - let certType = certSlugTypeMap[certSlug]; - log(certType); - return Observable.of(certTypeIds[certType]) - .flatMap(challenge => { - const certName = certTypeTitleMap[certType]; - if (user[certType]) { - return Observable.just({ - type: 'info', - message: 'flash.already-claimed', - variables: { name: certName } - }); - } - - // certificate doesn't exist or - // connection error - if (!challenge) { - reportError(`Error claiming ${certName}`); - return Observable.just({ - type: 'danger', - message: 'flash.wrong-name', - variables: { name: certName } - }); - } - - const { id, tests, challengeType } = challenge; - if (!hasCompletedTests(tests, user.completedChallenges)) { - return Observable.just({ - type: 'info', - message: 'flash.incomplete-steps', - variables: { name: certName } - }); - } - - const updateData = { - [certType]: true, - completedChallenges: [ - ...user.completedChallenges, - { - id, - completedDate: new Date(), - challengeType - } - ] - }; - - if (!user.name) { - return Observable.just({ - type: 'info', - message: 'flash.name-needed' - }); - } - // set here so sendCertifiedEmail works properly - // not used otherwise - user[certType] = true; - const updatePromise = new Promise((resolve, reject) => - user.updateAttributes(updateData, err => { - if (err) { - return reject(err); - } - return resolve(); - }) - ); - return Observable.combineLatest( - // update user data - Observable.fromPromise(updatePromise), - // sends notification email is user has all 6 certs - // if not it noop - sendCertifiedEmail(user, Email.send$), - (_, pledgeOrMessage) => ({ pledgeOrMessage }) - ).map(({ pledgeOrMessage }) => { - if (typeof pledgeOrMessage === 'string') { - log(pledgeOrMessage); - } - log('Certificates updated'); - return { - type: 'success', - message: 'flash.cert-claim-success', - variables: { - username: user.username, - name: certName - } - }; - }); - }) - .subscribe(message => { - return res.status(200).json({ - response: message, - isCertMap: getUserIsCertMap(user), - // send back the completed challenges - // NOTE: we could just send back the latest challenge, but this - // ensures the challenges are synced. - completedChallenges: user.completedChallenges - }); - }, next); - }; -} - -function createShowCert(app) { - const { User } = app.models; - - function findUserByUsername$(username, fields) { - return observeQuery(User, 'findOne', { - where: { username }, - fields - }); - } - - return function showCert(req, res, next) { - let { username, certSlug } = req.params; - username = username.toLowerCase(); - const certType = certSlugTypeMap[certSlug]; - const certId = certTypeIdMap[certType]; - const certTitle = certTypeTitleMap[certType]; - const completionTime = completionHours[certType] || 300; - return findUserByUsername$(username, { - isBanned: true, - isCheater: true, - isFrontEndCert: true, - isBackEndCert: true, - isFullStackCert: true, - isRespWebDesignCert: true, - isFrontEndLibsCert: true, - isJsAlgoDataStructCert: true, - isJsAlgoDataStructCertV8: true, - isDataVisCert: true, - is2018DataVisCert: true, - isApisMicroservicesCert: true, - isInfosecQaCert: true, - isQaCertV7: true, - isInfosecCertV7: true, - isSciCompPyCertV7: true, - isDataAnalysisPyCertV7: true, - isMachineLearningPyCertV7: true, - isRelationalDatabaseCertV8: true, - isCollegeAlgebraPyCertV8: true, - isFoundationalCSharpCertV8: true, - isHonest: true, - username: true, - name: true, - completedChallenges: true, - profileUI: true - }).subscribe(user => { - if (!user) { - return res.json({ - messages: [ - { - type: 'info', - message: 'flash.username-not-found', - variables: { username: username } - } - ] - }); - } - const { isLocked, showCerts, showName, showTimeLine } = user.profileUI; - - if (user.isCheater || user.isBanned) { - return res.json({ - messages: [ - { - type: 'info', - message: 'flash.not-eligible' - } - ] - }); - } - - if (!user.isHonest) { - return res.json({ - messages: [ - { - type: 'info', - message: 'flash.not-honest', - variables: { username: username } - } - ] - }); - } - - if (isLocked) { - return res.json({ - messages: [ - { - type: 'info', - message: 'flash.profile-private', - variables: { username: username } - } - ] - }); - } - - // If the user does not have a name, and have set their name to public, - // warn them. Otherwise, fallback to username - if (!user.name && user.showName) { - return res.json({ - messages: [ - { - type: 'info', - message: 'flash.add-name' - } - ] - }); - } - - if (!showCerts) { - return res.json({ - messages: [ - { - type: 'info', - message: 'flash.certs-private', - variables: { username: username } - } - ] - }); - } - - if (!showTimeLine) { - return res.json({ - messages: [ - { - type: 'info', - message: 'flash.timeline-private', - variables: { username: username } - } - ] - }); - } - - if (user[certType]) { - const { completedChallenges = [] } = user; - const certChallenge = _.find( - completedChallenges, - ({ id }) => certId === id - ); - let { completedDate = new Date() } = certChallenge || {}; - - // the challenge id has been rotated for isDataVisCert - if (certType === 'isDataVisCert' && !certChallenge) { - let oldDataVisIdChall = _.find( - completedChallenges, - ({ id }) => oldDataVizId === id - ); - - if (oldDataVisIdChall) { - completedDate = oldDataVisIdChall.completedDate || completedDate; - } - } - - // if fullcert is not found, return the latest completedDate - if (certType === 'isFullStackCert' && !certChallenge) { - completedDate = getFallbackFullStackDate( - completedChallenges, - completedDate - ); - } - - const { username, name } = user; - - if (!showName) { - return res.json({ - certSlug, - certTitle, - username, - date: completedDate, - completionTime - }); - } - - return res.json({ - certSlug, - certTitle, - username, - name, - date: completedDate, - completionTime - }); - } - return res.json({ - messages: [ - { - type: 'info', - message: 'flash.user-not-certified', - variables: { username: username, cert: certTypeTitleMap[certType] } - } - ] - }); - }, next); - }; -} diff --git a/api-server/src/server/boot/challenge.js b/api-server/src/server/boot/challenge.js deleted file mode 100644 index 3a0996e2e5a..00000000000 --- a/api-server/src/server/boot/challenge.js +++ /dev/null @@ -1,1031 +0,0 @@ -/** - * - * Any ref to fixCompletedChallengesItem should be removed post - * a db migration to fix all completedChallenges - * - * NOTE: it's been 4 years, so any active users will have been migrated. We - * should still try to migrate the rest at some point. - * - */ -import debug from 'debug'; -import dedent from 'dedent'; -import { isEmpty, pick, omit, uniqBy } from 'lodash'; -import { ObjectID } from 'mongodb'; -import isNumeric from 'validator/lib/isNumeric'; -import isURL from 'validator/lib/isURL'; -import fetch from 'node-fetch'; -import jwt from 'jsonwebtoken'; - -import { jwtSecret } from '../../../config/secrets'; -import { challengeTypes } from '../../../../shared/config/challenge-types'; - -import { - fixPartiallyCompletedChallengeItem, - fixCompletedExamItem -} from '../../common/utils'; -import { getChallenges } from '../utils/get-curriculum'; -import { ifNoUserSend } from '../utils/middleware'; -import { - getRedirectParams, - normalizeParams, - getPrefixedLandingPath -} from '../utils/redirection'; -import { generateRandomExam, createExamResults } from '../utils/exam'; -import { - validateExamFromDbSchema, - validateExamResultsSchema, - validateGeneratedExamSchema, - validateUserCompletedExamSchema -} from '../utils/exam-schemas'; - -const log = debug('fcc:boot:challenges'); - -export default async function bootChallenge(app, done) { - const send200toNonUser = ifNoUserSend(true); - const api = app.loopback.Router(); - const router = app.loopback.Router(); - const challengeUrlResolver = - await createChallengeUrlResolver(getChallenges()); - const redirectToCurrentChallenge = createRedirectToCurrentChallenge( - challengeUrlResolver, - normalizeParams, - getRedirectParams - ); - - api.post( - '/modern-challenge-completed', - send200toNonUser, - isValidChallengeCompletion, - modernChallengeCompleted - ); - - api.post( - '/project-completed', - send200toNonUser, - isValidChallengeCompletion, - projectCompleted - ); - - api.post( - '/backend-challenge-completed', - send200toNonUser, - isValidChallengeCompletion, - backendChallengeCompleted - ); - - const generateExam = createGenerateExam(app); - - api.get('/exam/:id', send200toNonUser, generateExam); - - const examChallengeCompleted = createExamChallengeCompleted(app); - - api.post( - '/exam-challenge-completed', - send200toNonUser, - examChallengeCompleted - ); - - api.post( - '/save-challenge', - send200toNonUser, - isValidChallengeCompletion, - saveChallenge - ); - - router.get('/challenges/current-challenge', redirectToCurrentChallenge); - - const coderoadChallengeCompleted = createCoderoadChallengeCompleted(app); - - api.post('/coderoad-challenge-completed', coderoadChallengeCompleted); - - const msTrophyChallengeCompleted = createMsTrophyChallengeCompleted(app); - - api.post( - '/ms-trophy-challenge-completed', - send200toNonUser, - msTrophyChallengeCompleted - ); - - app.use(api); - app.use(router); - done(); -} - -const jsCertProjectIds = [ - 'aaa48de84e1ecc7c742e1124', - 'a7f4d8f2483413a6ce226cac', - '56533eb9ac21ba0edf2244e2', - 'aff0395860f5d3034dc0bfc9', - 'aa2e6f85cab2ab736c9a9b24' -]; - -const multifileCertProjectIds = getChallenges() - .filter( - challenge => challenge.challengeType === challengeTypes.multifileCertProject - ) - .map(challenge => challenge.id); - -const multifilePythonCertProjectIds = getChallenges() - .filter( - challenge => - challenge.challengeType === challengeTypes.multifilePythonCertProject - ) - .map(challenge => challenge.id); - -const savableChallenges = getChallenges() - .filter(challenge => { - return ( - challenge.challengeType === challengeTypes.multifileCertProject || - challenge.challengeType === challengeTypes.multifilePythonCertProject - ); - }) - .map(challenge => challenge.id); - -const msTrophyChallenges = getChallenges() - .filter(challenge => challenge.challengeType === challengeTypes.msTrophy) - .map(({ id, msTrophyId }) => ({ id, msTrophyId })); - -export function buildUserUpdate( - user, - challengeId, - _completedChallenge, - timezone -) { - const { files, completedDate = Date.now() } = _completedChallenge; - let completedChallenge = {}; - if ( - jsCertProjectIds.includes(challengeId) || - multifileCertProjectIds.includes(challengeId) || - multifilePythonCertProjectIds.includes(challengeId) - ) { - completedChallenge = { - ..._completedChallenge, - files: files?.map(file => - pick(file, ['contents', 'key', 'index', 'name', 'path', 'ext']) - ) - }; - } else { - completedChallenge = omit(_completedChallenge, ['files']); - } - let finalChallenge; - const $push = {}, - $set = {}, - $pull = {}; - const { - timezone: userTimezone, - completedChallenges = [], - needsModeration = false, - savedChallenges = [] - } = user; - - const oldIndex = completedChallenges.findIndex( - ({ id }) => challengeId === id - ); - - const alreadyCompleted = oldIndex !== -1; - const oldChallenge = alreadyCompleted ? completedChallenges[oldIndex] : null; - - if (alreadyCompleted) { - finalChallenge = { - ...completedChallenge, - completedDate: oldChallenge.completedDate - }; - $set[`completedChallenges.${oldIndex}`] = finalChallenge; - } else { - finalChallenge = { - ...completedChallenge - }; - $push.progressTimestamps = completedDate; - $push.completedChallenges = finalChallenge; - } - - if (savableChallenges.includes(challengeId)) { - const challengeToSave = { - id: challengeId, - lastSavedDate: completedDate, - files: files?.map(file => - pick(file, ['contents', 'key', 'name', 'ext', 'history']) - ) - }; - - const savedIndex = savedChallenges.findIndex( - ({ id }) => challengeId === id - ); - - if (savedIndex >= 0) { - $set[`savedChallenges.${savedIndex}`] = challengeToSave; - savedChallenges[savedIndex] = challengeToSave; - } else { - $push.savedChallenges = challengeToSave; - savedChallenges.push(challengeToSave); - } - } - - // remove from partiallyCompleted on submit - $pull.partiallyCompletedChallenges = { id: challengeId }; - - if ( - timezone && - timezone !== 'UTC' && - (!userTimezone || userTimezone === 'UTC') - ) { - $set.timezone = userTimezone; - } - - if (needsModeration) $set.needsModeration = true; - - const updateData = {}; - if (!isEmpty($set)) updateData.$set = $set; - if (!isEmpty($push)) updateData.$push = $push; - if (!isEmpty($pull)) updateData.$pull = $pull; - - return { - alreadyCompleted, - updateData, - completedDate: finalChallenge.completedDate, - savedChallenges - }; -} - -export function buildExamUserUpdate(user, _completedChallenge) { - const { - id, - challengeType, - completedDate = Date.now(), - examResults - } = _completedChallenge; - - let finalChallenge = { id, challengeType, completedDate, examResults }; - - const { completedChallenges = [] } = user; - const $push = {}, - $set = {}; - - // Always push to completedExams[] to keep a record of all submissions, it may come in handy. - $push.completedExams = fixCompletedExamItem(_completedChallenge); - - let alreadyCompleted = false; - let addPoint = false; - - // completedChallenges[] should have their best exam - if (examResults.passed) { - const alreadyCompletedIndex = completedChallenges.findIndex( - challenge => challenge.id === id - ); - - alreadyCompleted = alreadyCompletedIndex !== -1; - - if (alreadyCompleted) { - const { percentCorrect } = examResults; - const oldChallenge = completedChallenges[alreadyCompletedIndex]; - const oldResults = oldChallenge.examResults; - - // only update if it's a better result - if (percentCorrect > oldResults.percentCorrect) { - finalChallenge.completedDate = oldChallenge.completedDate; - $set[`completedChallenges.${alreadyCompletedIndex}`] = finalChallenge; - } - } else { - addPoint = true; - $push.completedChallenges = finalChallenge; - } - } - - const updateData = {}; - if (!isEmpty($set)) updateData.$set = $set; - if (!isEmpty($push)) updateData.$push = $push; - - return { - alreadyCompleted, - addPoint, - updateData, - completedDate: finalChallenge.completedDate - }; -} - -export function buildChallengeUrl(challenge) { - const { superBlock, block, dashedName } = challenge; - return `/learn/${superBlock}/${block}/${dashedName}`; -} - -// this is only called once during boot, so it can be slow. -export function getFirstChallenge(allChallenges) { - const first = allChallenges.find( - ({ challengeOrder, superOrder, order }) => - challengeOrder === 0 && superOrder === 0 && order === 0 - ); - - return first ? buildChallengeUrl(first) : '/learn'; -} - -function getChallengeById(allChallenges, targetId) { - return allChallenges.find(({ id }) => id === targetId); -} - -export async function createChallengeUrlResolver( - allChallenges, - { _getFirstChallenge = getFirstChallenge } = {} -) { - const cache = new Map(); - const firstChallenge = _getFirstChallenge(allChallenges); - - return function resolveChallengeUrl(id) { - if (isEmpty(id)) { - return Promise.resolve(firstChallenge); - } else { - return new Promise(resolve => { - if (cache.has(id)) { - resolve(cache.get(id)); - } - - const challenge = getChallengeById(allChallenges, id); - if (isEmpty(challenge)) { - resolve(firstChallenge); - } else { - const challengeUrl = buildChallengeUrl(challenge); - cache.set(id, challengeUrl); - resolve(challengeUrl); - } - }); - } - }; -} - -export function isValidChallengeCompletion(req, res, next) { - const { - body: { id, challengeType, solution, githubLink } - } = req; - - // ToDO: Validate other things (challengeFiles, etc) - const isValidChallengeCompletionErrorMsg = { - type: 'error', - message: 'That does not appear to be a valid challenge submission.' - }; - - if (!ObjectID.isValid(id)) { - log('isObjectId', id, ObjectID.isValid(id)); - return res.status(403).json(isValidChallengeCompletionErrorMsg); - } - if ('challengeType' in req.body && !isNumeric(String(challengeType))) { - log('challengeType', challengeType, isNumeric(challengeType)); - return res.status(403).json(isValidChallengeCompletionErrorMsg); - } - // If `backEndProject`: - // - `solution` needs to exist, but does not have to be valid URL - // - `githubLink` needs to exist and be valid URL - if (challengeType === challengeTypes.backEndProject) { - if (!solution || !isURL(githubLink + '')) { - log('isObjectId', id, ObjectID.isValid(id)); - return res.status(403).json(isValidChallengeCompletionErrorMsg); - } - } else if ('solution' in req.body && !isURL(solution)) { - log('isObjectId', id, ObjectID.isValid(id)); - return res.status(403).json(isValidChallengeCompletionErrorMsg); - } - return next(); -} - -export async function modernChallengeCompleted(req, res, next) { - const user = req.user; - - try { - // This is an ugly way to update `user.completedChallenges` - await user.getCompletedChallenges$().toPromise(); - } catch (e) { - return next(e); - } - - const completedDate = Date.now(); - const { id, files, challengeType } = req.body; - - const completedChallenge = { - id, - files, - completedDate - }; - - // if multifile cert project - if (challengeType === 14) { - completedChallenge.isManuallyApproved = false; - user.needsModeration = true; - } - - // We only need to know the challenge type if it's a project. If it's a - // step or normal challenge we can avoid storing in the database. - if ( - jsCertProjectIds.includes(id) || - multifileCertProjectIds.includes(id) || - multifilePythonCertProjectIds.includes(id) - ) { - completedChallenge.challengeType = challengeType; - } - - const { alreadyCompleted, savedChallenges, updateData } = buildUserUpdate( - user, - id, - completedChallenge - ); - - const points = alreadyCompleted ? user.points : user.points + 1; - - user.updateAttributes(updateData, err => { - if (err) { - return next(err); - } - - return res.json({ - points, - alreadyCompleted, - completedDate, - savedChallenges - }); - }); -} - -async function projectCompleted(req, res, next) { - const { user, body = {} } = req; - - const completedChallenge = pick(body, [ - 'id', - 'solution', - 'githubLink', - 'challengeType', - 'files' - ]); - completedChallenge.completedDate = Date.now(); - - if ( - !completedChallenge.solution || - (completedChallenge.challengeType === challengeTypes.backEndProject && - !completedChallenge.githubLink) - ) { - return res.status(403).json({ - type: 'error', - message: - 'You have not provided the valid links for us to inspect your work.' - }); - } - - // CodeRoad cert project - if (completedChallenge.challengeType === 13) { - const { partiallyCompletedChallenges = [], completedChallenges = [] } = - user; - - const isPartiallyCompleted = partiallyCompletedChallenges.some( - challenge => challenge.id === completedChallenge.id - ); - - const isCompleted = completedChallenges.some( - challenge => challenge.id === completedChallenge.id - ); - - if (!isPartiallyCompleted && !isCompleted) { - return res.status(403).json({ - type: 'error', - message: 'You have to complete the project before you can submit a URL.' - }); - } - } - - try { - // This is an ugly hack to update `user.completedChallenges` - await user.getCompletedChallenges$().toPromise(); - } catch (e) { - return next(e); - } - - const { alreadyCompleted, updateData } = buildUserUpdate( - user, - completedChallenge.id, - completedChallenge - ); - - user.updateAttributes(updateData, err => { - if (err) { - return next(err); - } - - return res.json({ - alreadyCompleted, - points: alreadyCompleted ? user.points : user.points + 1, - completedDate: completedChallenge.completedDate - }); - }); -} - -async function backendChallengeCompleted(req, res, next) { - const { user, body = {} } = req; - - const completedChallenge = pick(body, ['id', 'solution']); - completedChallenge.completedDate = Date.now(); - - try { - await user.getCompletedChallenges$().toPromise(); - } catch (e) { - return next(e); - } - - const { alreadyCompleted, updateData } = buildUserUpdate( - user, - completedChallenge.id, - completedChallenge - ); - - user.updateAttributes(updateData, err => { - if (err) { - return next(err); - } - - return res.json({ - alreadyCompleted, - points: alreadyCompleted ? user.points : user.points + 1, - completedDate: completedChallenge.completedDate - }); - }); -} - -// TODO: send flash message keys to client so they can be i18n -function createGenerateExam(app) { - const { Exam } = app.models; - - return async function generateExam(req, res, next) { - const { - user, - params: { id } - } = req; - - try { - await user.getCompletedChallenges$().toPromise(); - } catch (e) { - return next(e); - } - - try { - const examFromDb = await Exam.findById(id); - if (!examFromDb) { - res.status(500); - throw new Error( - `An error occurred trying to get the exam from the database.` - ); - } - - // This is cause there was struggles validating the exam directly from the db/loopback - const examJson = JSON.parse(JSON.stringify(examFromDb)); - - const validExamFromDbSchema = validateExamFromDbSchema(examJson); - - if (validExamFromDbSchema.error) { - res.status(500); - log(validExamFromDbSchema.error); - throw new Error( - `An error occurred validating the exam information from the database.` - ); - } - - const { prerequisites, numberOfQuestionsInExam, title } = examJson; - - // Validate User has completed prerequisite challenges - prerequisites?.forEach(prerequisite => { - const prerequisiteCompleted = user.completedChallenges.find( - challenge => challenge.id === prerequisite.id - ); - - if (!prerequisiteCompleted) { - res.status(403); - throw new Error( - `You have not completed the required challenges to start the '${title}'.` - ); - } - }); - - const randomizedExam = generateRandomExam(examJson); - - const validGeneratedExamSchema = validateGeneratedExamSchema( - randomizedExam, - numberOfQuestionsInExam - ); - - if (validGeneratedExamSchema.error) { - res.status(500); - log(validGeneratedExamSchema.error); - throw new Error(`An error occurred trying to randomize the exam.`); - } - - return res.send({ generatedExam: randomizedExam }); - } catch (err) { - log(err); - return res.send({ error: err.message }); - } - }; -} - -function createExamChallengeCompleted(app) { - const { Exam } = app.models; - - return async function examChallengeCompleted(req, res, next) { - const { body = {}, user } = req; - - try { - await user.getCompletedChallenges$().toPromise(); - } catch (e) { - return next(e); - } - - const { userCompletedExam = [], id } = body; - - try { - const examFromDb = await Exam.findById(id); - if (!examFromDb) { - res.status(500); - throw new Error( - `An error occurred tryng to get the exam from the database.` - ); - } - - // This is cause there was struggles validating the exam directly from the db/loopback - const examJson = JSON.parse(JSON.stringify(examFromDb)); - - const validExamFromDbSchema = validateExamFromDbSchema(examJson); - if (validExamFromDbSchema.error) { - res.status(500); - log(validExamFromDbSchema.error); - throw new Error( - `An error occurred validating the exam information from the database.` - ); - } - - const { prerequisites, numberOfQuestionsInExam, title } = examJson; - - // Validate User has completed prerequisite challenges - prerequisites?.forEach(prerequisite => { - const prerequisiteCompleted = user.completedChallenges.find( - challenge => challenge.id === prerequisite.id - ); - - if (!prerequisiteCompleted) { - res.status(403); - throw new Error( - `You have not completed the required challenges to start the '${title}'.` - ); - } - }); - - // Validate user completed exam - const validUserCompletedExam = validateUserCompletedExamSchema( - userCompletedExam, - numberOfQuestionsInExam - ); - if (validUserCompletedExam.error) { - res.status(400); - log(validUserCompletedExam.error); - throw new Error(`An error occurred validating the submitted exam.`); - } - - const examResults = createExamResults(userCompletedExam, examJson); - - const validExamResults = validateExamResultsSchema(examResults); - if (validExamResults.error) { - res.status(500); - log(validExamResults.error); - throw new Error(`An error occurred validating the submitted exam.`); - } - - const completedChallenge = pick(body, ['id', 'challengeType']); - completedChallenge.completedDate = Date.now(); - completedChallenge.examResults = examResults; - - const { addPoint, alreadyCompleted, updateData, completedDate } = - buildExamUserUpdate(user, completedChallenge); - - user.updateAttributes(updateData, err => { - if (err) { - return next(err); - } - - const points = addPoint ? user.points + 1 : user.points; - - return res.json({ - alreadyCompleted, - points, - completedDate, - examResults - }); - }); - } catch (err) { - log(err); - return res.send({ error: err.message }); - } - }; -} - -function createMsTrophyChallengeCompleted(app) { - const { MsUsername } = app.models; - - return async function msTrophyChallengeCompleted(req, res, next) { - const { user, body = {} } = req; - const { id = '' } = body; - - try { - const msUser = await MsUsername.findOne({ - where: { userId: user.id } - }); - - if (!msUser || !msUser.msUsername) { - return res - .status(403) - .json({ type: 'error', message: 'flash.ms.trophy.err-1' }); - } - - const { msUsername } = msUser; - - const challenge = msTrophyChallenges.find( - challenge => challenge.id === id - ); - - if (!challenge) { - return res - .status(400) - .json({ type: 'error', message: 'flash.ms.trophy.err-2' }); - } - - const { msTrophyId = '' } = challenge; - - const msProfileApi = `https://learn.microsoft.com/api/profiles/${msUsername}`; - const msProfileApiRes = await fetch(msProfileApi); - const msProfileJson = await msProfileApiRes.json(); - - if (!msProfileApiRes.ok || !msProfileJson.userId) { - return res.status(403).json({ - type: 'error', - message: 'flash.ms.profile.err', - variables: { - msUsername - } - }); - } - - const { userId } = msProfileJson; - - const msUserAchievementsApi = `https://learn.microsoft.com/api/achievements/user/${userId}`; - const msUserAchievementsApiRes = await fetch(msUserAchievementsApi); - const msUserAchievementsJson = await msUserAchievementsApiRes.json(); - - if (!msUserAchievementsApiRes.ok) { - return res.status(403).json({ - type: 'error', - message: 'flash.ms.trophy.err-3' - }); - } - - if (msUserAchievementsJson.achievements?.length === 0) { - return res.status(403).json({ - type: 'error', - message: 'flash.ms.trophy.err-6' - }); - } - - const hasEarnedTrophy = msUserAchievementsJson.achievements?.some( - a => a.typeId === msTrophyId - ); - - if (!hasEarnedTrophy) { - return res.status(403).json({ - type: 'error', - message: 'flash.ms.trophy.err-4', - variables: { - msUsername - } - }); - } - - const completedChallenge = pick(body, ['id']); - - completedChallenge.solution = msUserAchievementsApi; - completedChallenge.completedDate = Date.now(); - - try { - await user.getCompletedChallenges$().toPromise(); - } catch (e) { - return next(e); - } - - const { alreadyCompleted, updateData } = buildUserUpdate( - user, - completedChallenge.id, - completedChallenge - ); - - user.updateAttributes(updateData, err => { - if (err) { - return next(err); - } - - return res.json({ - alreadyCompleted, - points: alreadyCompleted ? user.points : user.points + 1, - completedDate: completedChallenge.completedDate - }); - }); - } catch (e) { - log(e); - return res.status(500).json({ - type: 'error', - message: 'flash.ms.trophy.err-5' - }); - } - }; -} - -async function saveChallenge(req, res, next) { - const user = req.user; - const { savedChallenges = [] } = user; - const { id: challengeId, files = [] } = req.body; - - if (!savableChallenges.includes(challengeId)) { - return res.status(403).send('That challenge type is not savable'); - } - - const challengeToSave = { - id: challengeId, - lastSavedDate: Date.now(), - files: files?.map(file => - pick(file, ['contents', 'key', 'name', 'ext', 'history']) - ) - }; - - try { - await user.getSavedChallenges$().toPromise(); - } catch (e) { - return next(e); - } - - const savedIndex = savedChallenges.findIndex(({ id }) => challengeId === id); - const $push = {}, - $set = {}; - - if (savedIndex >= 0) { - $set[`savedChallenges.${savedIndex}`] = challengeToSave; - savedChallenges[savedIndex] = challengeToSave; - } else { - $push.savedChallenges = challengeToSave; - savedChallenges.push(challengeToSave); - } - - const updateData = {}; - if (!isEmpty($set)) updateData.$set = $set; - if (!isEmpty($push)) updateData.$push = $push; - - user.updateAttributes(updateData, err => { - if (err) { - return next(err); - } - - return res.json({ - savedChallenges - }); - }); -} - -const codeRoadChallenges = getChallenges().filter( - ({ challengeType }) => challengeType === 12 || challengeType === 13 -); - -function createCoderoadChallengeCompleted(app) { - /* Example request coming from CodeRoad: - * req.body: { tutorialId: 'freeCodeCamp/learn-bash-by-building-a-boilerplate:v1.0.0' } - * req.headers: { coderoad-user-token: '8kFIlZiwMioY6hqqt...' } - */ - - const { UserToken, User } = app.models; - - return async function coderoadChallengeCompleted(req, res) { - const { 'coderoad-user-token': encodedUserToken } = req.headers; - const { tutorialId } = req.body; - - if (!tutorialId) return res.send(`'tutorialId' not found in request body`); - - if (!encodedUserToken) - return res.send(`'coderoad-user-token' not found in request headers`); - - let userToken; - - try { - userToken = jwt.verify(encodedUserToken, jwtSecret)?.userToken; - } catch { - return res.send(`invalid user token`); - } - - const tutorialRepo = tutorialId?.split(':')[0]; - const tutorialOrg = tutorialRepo?.split('/')?.[0]; - - if (tutorialOrg !== 'freeCodeCamp') - return res.send('Tutorial not hosted on freeCodeCamp GitHub account'); - - // validate tutorial name is in codeRoadChallenges object - const challenge = codeRoadChallenges.find(challenge => - challenge.url?.endsWith(tutorialRepo) - ); - - if (!challenge) return res.send('Tutorial name is not valid'); - - const { id: challengeId, challengeType } = challenge; - - try { - // check if user token is in database - const tokenInfo = await UserToken.findOne({ - where: { id: userToken } - }); - - if (!tokenInfo) return res.send('User token not found'); - - const { userId } = tokenInfo; - - // check if user exists for user token - const user = await User.findOne({ - where: { id: userId } - }); - - if (!user) return res.send('User for user token not found'); - - // submit challenge - const completedDate = Date.now(); - const { completedChallenges = [], partiallyCompletedChallenges = [] } = - user; - - let userUpdateInfo = {}; - - const isCompleted = completedChallenges.some( - challenge => challenge.id === challengeId - ); - - // if CodeRoad cert project and not in completedChallenges, - // add to partiallyCompletedChallenges - if (challengeType === 13 && !isCompleted) { - const finalChallenge = { - id: challengeId, - completedDate - }; - - userUpdateInfo.updateData = {}; - userUpdateInfo.updateData.$set = { - partiallyCompletedChallenges: uniqBy( - [ - finalChallenge, - ...partiallyCompletedChallenges.map( - fixPartiallyCompletedChallengeItem - ) - ], - 'id' - ) - }; - - // else, add to or update completedChallenges - } else { - userUpdateInfo = buildUserUpdate(user, challengeId, { - id: challengeId, - completedDate - }); - } - const updatedUser = await user.updateAttributes( - userUpdateInfo?.updateData - ); - - if (!updatedUser) - return res.send('An error occurred trying to submit the challenge'); - } catch (e) { - return res.send('An error occurred trying to submit the challenge'); - } - - return res.send('Successfully submitted challenge'); - }; -} - -// TODO: extend tests to cover www.freecodecamp.org/language and -// chinese.freecodecamp.org -export function createRedirectToCurrentChallenge( - challengeUrlResolver, - normalizeParams, - getRedirectParams -) { - return async function redirectToCurrentChallenge(req, res, next) { - const { user } = req; - const { origin, pathPrefix } = getRedirectParams(req, normalizeParams); - - const redirectBase = getPrefixedLandingPath(origin, pathPrefix); - if (!user) { - return res.redirect(redirectBase + '/learn'); - } - - const challengeId = user && user.currentChallengeId; - const challengeUrl = await challengeUrlResolver(challengeId).catch(next); - if (challengeUrl === '/learn') { - // this should normally not be hit if database is properly seeded - throw new Error(dedent` - Attempted to find the url for ${challengeId || 'Unknown ID'}' - but came up empty. - db may not be properly seeded. - `); - } - return res.redirect(`${redirectBase}${challengeUrl}`); - }; -} diff --git a/api-server/src/server/boot/donate.js b/api-server/src/server/boot/donate.js deleted file mode 100644 index 05aee1ac93e..00000000000 --- a/api-server/src/server/boot/donate.js +++ /dev/null @@ -1,189 +0,0 @@ -import debug from 'debug'; -import Stripe from 'stripe'; - -import { - donationSubscriptionConfig, - allStripeProductIdsArray -} from '../../../../shared/config/donation-settings'; -import keys from '../../../config/secrets'; -import { - createStripeCardDonation, - handleStripeCardUpdateSession, - inLastFiveMinutes -} from '../utils/donation'; -import { validStripeForm } from '../utils/stripeHelpers'; - -const log = debug('fcc:boot:donate'); - -export default function donateBoot(app, done) { - let stripe = false; - const { User } = app.models; - const api = app.loopback.Router(); - const hooks = app.loopback.Router(); - const donateRouter = app.loopback.Router(); - - function connectToStripe() { - return new Promise(function () { - // connect to stripe API - stripe = Stripe(keys.stripe.secret); - }); - } - - async function handleStripeCardDonation(req, res) { - return createStripeCardDonation(req, res, stripe, app).catch(err => { - if ( - err.type === 'AlreadyDonatingError' || - err.type === 'UserActionRequired' || - err.type === 'PaymentMethodRequired' - ) { - return res.status(402).send({ error: err }); - } - if (err.type === 'InvalidRequest') - return res.status(400).send({ error: err }); - return res.status(500).send({ - error: 'Donation failed due to a server error.' - }); - }); - } - - async function createStripeDonation(req, res) { - const { body } = req; - const { amount, duration, email, subscriptionId } = body; - try { - const subscription = await stripe.subscriptions.retrieve(subscriptionId); - const isSubscriptionActive = subscription.status === 'active'; - const productId = subscription.items.data[0].plan.product; - const isStartedRecently = inLastFiveMinutes( - subscription.current_period_start - ); - const isProductIdValid = allStripeProductIdsArray.includes(productId); - - if (isSubscriptionActive && isProductIdValid && isStartedRecently) { - const [donatingUser] = await User.findOrCreate( - { where: { email } }, - { email } - ); - const donation = { - email, - amount, - duration, - provider: 'stripe', - subscriptionId, - customerId: subscription.customer, - startDate: new Date().toISOString() - }; - await donatingUser.createDonation(donation); - return res.status(200).send({ isDonating: true }); - } else { - throw new Error('Donation failed due to a server error.'); - } - } catch (err) { - return res - .status(500) - .send({ error: 'Donation failed due to a server error.' }); - } - } - - async function createStripePaymentIntent(req, res) { - const { body } = req; - const { amount, duration, email, name } = body; - - if (!validStripeForm(amount, duration, email)) { - return res.status(400).send({ - error: 'The donation form had invalid values for this submission.' - }); - } - - try { - const stripeCustomer = await stripe.customers.create({ - email, - name - }); - - const stripeSubscription = await stripe.subscriptions.create({ - customer: stripeCustomer.id, - items: [ - { - plan: `${donationSubscriptionConfig.duration[duration]}-donation-${amount}` - } - ], - payment_behavior: 'default_incomplete', - payment_settings: { save_default_payment_method: 'on_subscription' }, - expand: ['latest_invoice.payment_intent'] - }); - - res.status(200).send({ - subscriptionId: stripeSubscription.id, - clientSecret: - stripeSubscription.latest_invoice.payment_intent.client_secret - }); - } catch (err) { - return res - .status(500) - .send({ error: 'Donation failed due to a server error.' }); - } - } - - function addDonation(req, res) { - const { user, body } = req; - if (!user || !body) { - return res - .status(500) - .json({ error: 'User must be signed in for this request.' }); - } - return Promise.resolve(req) - .then( - user.updateAttributes({ - isDonating: true - }) - ) - .then(() => res.status(200).json({ isDonating: true })) - .catch(err => { - log(err.message); - return res.status(500).json({ - type: 'danger', - message: 'Something went wrong.' - }); - }); - } - - async function handleStripeCardUpdate(req, res, next) { - try { - const sessionIdObj = await handleStripeCardUpdateSession( - req, - app, - stripe - ); - return res.status(200).json(sessionIdObj); - } catch (err) { - return next(err); - } - } - - const stripeKey = keys.stripe.public; - const secKey = keys.stripe.secret; - const stripeSecretInvalid = !secKey || secKey === 'sk_from_stripe_dashboard'; - const stripPublicInvalid = - !stripeKey || stripeKey === 'pk_from_stripe_dashboard'; - - const stripeInvalid = stripeSecretInvalid || stripPublicInvalid; - - if (stripeInvalid) { - if (process.env.FREECODECAMP_NODE_ENV === 'production') { - throw new Error('Donation API keys are required to boot the server!'); - } - log('Donation disabled in development unless ALL test keys are provided'); - done(); - } else { - api.post('/charge-stripe', createStripeDonation); - api.post('/charge-stripe-card', handleStripeCardDonation); - api.post('/create-stripe-payment-intent', createStripePaymentIntent); - api.put('/update-stripe-card', handleStripeCardUpdate); - api.post('/add-donation', addDonation); - donateRouter.use('/donate', api); - donateRouter.use('/hooks', hooks); - app.use(donateRouter); - connectToStripe(stripe).then(done); - done(); - } -} diff --git a/api-server/src/server/boot/explorer.js b/api-server/src/server/boot/explorer.js deleted file mode 100644 index 8de921c416e..00000000000 --- a/api-server/src/server/boot/explorer.js +++ /dev/null @@ -1,33 +0,0 @@ -const createDebugger = require('debug'); - -const log = createDebugger('fcc:boot:explorer'); - -module.exports = function mountLoopBackExplorer(app) { - if (process.env.FREECODECAMP_NODE_ENV === 'production') { - return; - } - let explorer; - try { - explorer = require('loopback-component-explorer'); - } catch (err) { - // Print the message only when the app was started via `app.listen()`. - // Do not print any message when the project is used as a component. - app.once('started', function () { - log( - 'Run `pnpm add loopback-component-explorer` to enable ' + - 'the LoopBack explorer' - ); - }); - return; - } - - const restApiRoot = app.get('restApiRoot'); - const mountPath = '/explorer'; - - explorer(app, { basePath: restApiRoot, mountPath }); - app.once('started', function () { - const baseUrl = app.get('url').replace(/\/$/, ''); - - log('Browse your REST API at %s%s', baseUrl, mountPath); - }); -}; diff --git a/api-server/src/server/boot/news.js b/api-server/src/server/boot/news.js deleted file mode 100644 index a8c22e60f51..00000000000 --- a/api-server/src/server/boot/news.js +++ /dev/null @@ -1,41 +0,0 @@ -import debug from 'debug'; - -const log = debug('fcc:boot:news'); - -export default function newsBoot(app) { - const router = app.loopback.Router(); - - router.get('/n', (req, res) => res.redirect('/news')); - router.get('/n/:shortId', createShortLinkHandler(app)); -} - -function createShortLinkHandler(app) { - const { Article } = app.models; - - return function shortLinkHandler(req, res, next) { - const { shortId } = req.params; - - if (!shortId) { - return res.redirect('/news'); - } - log('shortId', shortId); - return Article.findOne( - { - where: { - or: [{ shortId }, { slugPart: shortId }] - } - }, - (err, article) => { - if (err) { - next(err); - } - if (!article) { - return res.redirect('/news'); - } - const { slugPart } = article; - const slug = `/news/${slugPart}`; - return res.redirect(slug); - } - ); - }; -} diff --git a/api-server/src/server/boot/randomAPIs.js b/api-server/src/server/boot/randomAPIs.js deleted file mode 100644 index 4d238796304..00000000000 --- a/api-server/src/server/boot/randomAPIs.js +++ /dev/null @@ -1,242 +0,0 @@ -import { pick } from 'lodash'; -import { getRedirectParams } from '../utils/redirection'; -import { deprecatedEndpoint } from '../utils/disabled-endpoints'; -import { - getProgress, - normaliseUserFields, - publicUserProps -} from '../utils/publicUserProps'; - -module.exports = function (app) { - const router = app.loopback.Router(); - const User = app.models.User; - - router.get('/api/github', deprecatedEndpoint); - router.get('/u/:email', unsubscribeDeprecated); - router.get('/unsubscribe/:email', unsubscribeDeprecated); - router.get('/ue/:unsubscribeId', unsubscribeById); - router.get('/resubscribe/:unsubscribeId', resubscribe); - router.get('/users/get-public-profile', blockUserAgent, getPublicProfile); - const getUserExists = createGetUserExists(app); - router.get('/users/exists', getUserExists); - - app.use(router); - - function unsubscribeDeprecated(req, res) { - req.flash( - 'info', - 'We are no longer able to process this unsubscription request. ' + - 'Please go to your settings to update your email preferences' - ); - const { origin } = getRedirectParams(req); - res.redirectWithFlash(origin); - } - - function unsubscribeById(req, res, next) { - const { origin } = getRedirectParams(req); - const { unsubscribeId } = req.params; - if (!unsubscribeId) { - req.flash('info', 'We could not find an account to unsubscribe'); - return res.redirectWithFlash(origin); - } - return User.find({ where: { unsubscribeId } }, (err, users) => { - if (err || !users.length) { - req.flash('info', 'We could not find an account to unsubscribe'); - return res.redirectWithFlash(origin); - } - const updates = users.map(user => { - return new Promise((resolve, reject) => - user.updateAttributes( - { - sendQuincyEmail: false - }, - err => { - if (err) { - reject(err); - } else { - resolve(); - } - } - ) - ); - }); - return Promise.all(updates) - .then(() => { - req.flash( - 'success', - "We've successfully updated your email preferences." - ); - return res.redirectWithFlash( - `${origin}/unsubscribed/${unsubscribeId}` - ); - }) - .catch(next); - }); - } - - function resubscribe(req, res, next) { - const { unsubscribeId } = req.params; - const { origin } = getRedirectParams(req); - if (!unsubscribeId) { - req.flash( - 'info', - 'We were unable to process this request, please check and try again' - ); - res.redirect(origin); - } - return User.find({ where: { unsubscribeId } }, (err, users) => { - if (err || !users.length) { - req.flash('info', 'We could not find an account to resubscribe'); - return res.redirectWithFlash(origin); - } - const [user] = users; - return new Promise((resolve, reject) => - user.updateAttributes( - { - sendQuincyEmail: true - }, - err => { - if (err) { - reject(err); - } else { - resolve(); - } - } - ) - ) - .then(() => { - req.flash( - 'success', - "We've successfully updated your email preferences. Thank you for resubscribing." - ); - return res.redirectWithFlash(origin); - }) - .catch(next); - }); - } - - const blockedUserAgentParts = ['python', 'google-apps-script', 'curl']; - - function blockUserAgent(req, res, next) { - const userAgent = req.headers['user-agent']; - - if ( - !userAgent || - blockedUserAgentParts.some(ua => userAgent.toLowerCase().includes(ua)) - ) { - return res - .status(400) - .send( - 'This endpoint is no longer available outside of the freeCodeCamp ecosystem' - ); - } - - return next(); - } - - async function getPublicProfile(req, res) { - const { username } = req.query; - if (!username) { - return res.status(400).json({ error: 'No username provided' }); - } - - const user = await User.findOne({ where: { username } }); - - if (!user) { - return res.status(404).json({ error: 'User not found' }); - } - - const { completedChallenges, progressTimestamps, profileUI } = user; - const allUser = { - ...pick(user, publicUserProps), - points: progressTimestamps.length, - completedChallenges, - ...getProgress(progressTimestamps), - ...normaliseUserFields(user), - joinDate: user.id.getTimestamp() - }; - - const publicUser = prepUserForPublish(allUser, profileUI); - - return res.json({ - entities: { - user: { - [user.username]: { - ...publicUser - } - } - }, - result: user.username - }); - } - - function createGetUserExists(app) { - const User = app.models.User; - return function getUserExists(req, res) { - const username = req.query.username.toLowerCase(); - - User.doesExist(username, null).then(exists => { - res.send({ exists }); - }); - }; - } - - function prepUserForPublish(user, profileUI) { - const { - about, - calendar, - completedChallenges, - isDonating, - joinDate, - location, - name, - points, - portfolio, - username, - yearsTopContributor - } = user; - const { - isLocked = true, - showAbout = false, - showCerts = false, - showDonation = false, - showHeatMap = false, - showLocation = false, - showName = false, - showPoints = false, - showPortfolio = false, - showTimeLine = false - } = profileUI; - - if (isLocked) { - return { - isLocked, - profileUI, - username - }; - } - return { - ...user, - about: showAbout ? about : '', - calendar: showHeatMap ? calendar : {}, - completedChallenges: (function () { - if (showTimeLine) { - return showCerts - ? completedChallenges - : completedChallenges.filter( - ({ challengeType }) => challengeType !== 7 - ); - } else { - return []; - } - })(), - isDonating: showDonation ? isDonating : null, - joinDate: showAbout ? joinDate : '', - location: showLocation ? location : '', - name: showName ? name : '', - points: showPoints ? points : null, - portfolio: showPortfolio ? portfolio : [], - yearsTopContributor: yearsTopContributor - }; - } -}; diff --git a/api-server/src/server/boot/restApi.js b/api-server/src/server/boot/restApi.js deleted file mode 100644 index 73656db713a..00000000000 --- a/api-server/src/server/boot/restApi.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = function mountRestApi(app) { - const restApi = app.loopback.rest(); - const restApiRoot = app.get('restApiRoot'); - app.use(restApiRoot, restApi); -}; diff --git a/api-server/src/server/boot/settings.js b/api-server/src/server/boot/settings.js deleted file mode 100644 index fa8660276ce..00000000000 --- a/api-server/src/server/boot/settings.js +++ /dev/null @@ -1,373 +0,0 @@ -import debug from 'debug'; -import { check } from 'express-validator'; -import _ from 'lodash'; -import isURL from 'validator/lib/isURL'; - -import { isValidUsername } from '../../../../shared/utils/validate'; -import { alertTypes } from '../../common/utils/flash.js'; -import { - deprecatedEndpoint, - temporarilyDisabledEndpoint -} from '../utils/disabled-endpoints'; -import { ifNoUser401, createValidatorErrorHandler } from '../utils/middleware'; - -const log = debug('fcc:boot:settings'); - -export default function settingsController(app) { - const api = app.loopback.Router(); - - const updateMyUsername = createUpdateMyUsername(app); - - api.put('/update-privacy-terms', ifNoUser401, updatePrivacyTerms); - - api.post('/refetch-user-completed-challenges', deprecatedEndpoint); - // Re-enable once we can handle the traffic - // api.post( - // '/update-my-current-challenge', - // ifNoUser401, - // updateMyCurrentChallengeValidators, - // createValidatorErrorHandler(alertTypes.danger), - // updateMyCurrentChallenge - // ); - api.post('/update-my-current-challenge', temporarilyDisabledEndpoint); - api.put('/update-my-portfolio', ifNoUser401, updateMyPortfolio); - api.put('/update-my-theme', ifNoUser401, updateMyTheme); - api.put('/update-my-about', ifNoUser401, updateMyAbout); - api.put( - '/update-my-email', - ifNoUser401, - updateMyEmailValidators, - createValidatorErrorHandler(alertTypes.danger), - updateMyEmail - ); - api.put('/update-my-profileui', ifNoUser401, updateMyProfileUI); - api.put('/update-my-username', ifNoUser401, updateMyUsername); - api.put('/update-user-flag', ifNoUser401, updateUserFlag); - api.put('/update-my-socials', ifNoUser401, updateMySocials); - api.put( - '/update-my-keyboard-shortcuts', - ifNoUser401, - updateMyKeyboardShortcuts - ); - api.put('/update-my-honesty', ifNoUser401, updateMyHonesty); - api.put('/update-my-quincy-email', ifNoUser401, updateMyQuincyEmail); - api.put('/update-my-classroom-mode', ifNoUser401, updateMyClassroomMode); - - app.use(api); -} - -const standardErrorMessage = { - type: 'danger', - message: 'flash.wrong-updating' -}; - -const createStandardHandler = (req, res, next, alertMessage) => err => { - if (err) { - res.status(500).json(standardErrorMessage); - return next(err); - } - return res.status(200).json({ type: 'success', message: alertMessage }); -}; - -const createUpdateUserProperties = (buildUpdate, validate, successMessage) => { - return (req, res, next) => { - const { user, body } = req; - const update = buildUpdate(body); - if (validate(update)) { - user.updateAttributes( - update, - createStandardHandler(req, res, next, successMessage) - ); - } else { - handleInvalidUpdate(res); - } - }; -}; - -const updateMyEmailValidators = [ - check('email').isEmail().withMessage('Email format is invalid.') -]; - -function updateMyEmail(req, res, next) { - const { - user, - body: { email } - } = req; - return user - .requestUpdateEmail(email) - .subscribe( - message => res.json({ type: message.type, message: message.message }), - next - ); -} - -// Re-enable once we can handle the traffic -// const updateMyCurrentChallengeValidators = [ -// check('currentChallengeId') -// .isMongoId() -// .withMessage('currentChallengeId is not a valid challenge ID') -// ]; - -// Re-enable once we can handle the traffic -// function updateMyCurrentChallenge(req, res, next) { -// const { -// user, -// body: { currentChallengeId } -// } = req; -// return user.updateAttribute( -// 'currentChallengeId', -// currentChallengeId, -// (err, updatedUser) => { -// if (err) { -// return next(err); -// } -// const { currentChallengeId } = updatedUser; -// return res.status(200).json(currentChallengeId); -// } -// ); -// } - -function updateMyPortfolio(...args) { - const portfolioKeys = ['id', 'title', 'description', 'url', 'image']; - const buildUpdate = body => { - const portfolio = body?.portfolio?.map(elem => _.pick(elem, portfolioKeys)); - return { portfolio }; - }; - const validate = ({ portfolio }) => portfolio?.every(isPortfolioElement); - const isPortfolioElement = elem => - Object.values(elem).every(val => typeof val == 'string'); - createUpdateUserProperties( - buildUpdate, - validate, - 'flash.portfolio-item-updated' - )(...args); -} - -// This API is responsible for what campers decide to make public in their profile, and what is private. -function updateMyProfileUI(req, res, next) { - const { - user, - body: { profileUI } - } = req; - - const update = { - isLocked: !!profileUI.isLocked, - showAbout: !!profileUI.showAbout, - showCerts: !!profileUI.showCerts, - showDonation: !!profileUI.showDonation, - showHeatMap: !!profileUI.showHeatMap, - showLocation: !!profileUI.showLocation, - showName: !!profileUI.showName, - showPoints: !!profileUI.showPoints, - showPortfolio: !!profileUI.showPortfolio, - showTimeLine: !!profileUI.showTimeLine - }; - - user.updateAttribute( - 'profileUI', - update, - createStandardHandler(req, res, next, 'flash.privacy-updated') - ); -} - -export function updateMyAbout(req, res, next) { - const { - user, - body: { name, location, about, picture } - } = req; - log(name, location, picture, about); - // prevent dataurls from being stored - const update = isURL(picture, { require_protocol: true }) - ? { name, location, about, picture } - : { name, location, about, picture: '' }; - return user.updateAttributes( - update, - createStandardHandler(req, res, next, 'flash.updated-about-me') - ); -} - -function createUpdateMyUsername(app) { - const { User } = app.models; - return async function updateMyUsername(req, res, next) { - const { user, body } = req; - const usernameDisplay = body.username.trim(); - const username = usernameDisplay.toLowerCase(); - if ( - username === user.username && - user.usernameDisplay && - usernameDisplay === user.usernameDisplay - ) { - return res.json({ - type: 'info', - message: 'flash.username-used' - }); - } - const validation = isValidUsername(username); - - if (!validation.valid) { - return res.json({ - type: 'info', - message: `Username ${username} ${validation.error}` - }); - } - - const exists = - username === user.username ? false : await User.doesExist(username); - - if (exists) { - return res.json({ - type: 'info', - message: 'flash.username-taken' - }); - } - - return user.updateAttributes({ username, usernameDisplay }, err => { - if (err) { - res.status(500).json(standardErrorMessage); - return next(err); - } - - return res.status(200).json({ - type: 'success', - message: `flash.username-updated`, - variables: { username: usernameDisplay } - }); - }); - }; -} - -const updatePrivacyTerms = (req, res, next) => { - const { - user, - body: { quincyEmails } - } = req; - const update = { - acceptedPrivacyTerms: true, - sendQuincyEmail: !!quincyEmails - }; - return user.updateAttributes( - update, - createStandardHandler(req, res, next, 'flash.privacy-updated') - ); -}; - -const allowedSocialsAndDomains = { - githubProfile: 'github.com', - linkedin: 'linkedin.com', - twitter: ['twitter.com', 'x.com'], - website: '' -}; - -const socialVals = Object.keys(allowedSocialsAndDomains); - -export function updateMySocials(...args) { - const buildUpdate = body => _.pick(body, socialVals); - const validate = update => { - // Socials should point to their respective domains - // or be empty strings - return Object.keys(update).every(key => { - const val = update[key]; - if (val === '') { - return true; - } - if (key === 'website') { - return isURL(val, { require_protocol: true }); - } - - const domain = allowedSocialsAndDomains[key]; - - try { - const url = new URL(val); - const topDomain = url.hostname.split('.').slice(-2); - if (topDomain.length === 2) { - return Array.isArray(domain) - ? domain.some(d => topDomain.join('.') === d) - : topDomain.join('.') === domain; - } - return false; - } catch (e) { - return false; - } - }); - }; - createUpdateUserProperties( - buildUpdate, - validate, - 'flash.updated-socials' - )(...args); -} - -function updateMyTheme(...args) { - const buildUpdate = body => _.pick(body, 'theme'); - const validate = ({ theme }) => theme == 'default' || theme == 'night'; - createUpdateUserProperties( - buildUpdate, - validate, - 'flash.updated-themes' - )(...args); -} - -function updateMyKeyboardShortcuts(...args) { - const buildUpdate = body => _.pick(body, 'keyboardShortcuts'); - const validate = ({ keyboardShortcuts }) => - typeof keyboardShortcuts === 'boolean'; - createUpdateUserProperties( - buildUpdate, - validate, - 'flash.keyboard-shortcut-updated' - )(...args); -} - -function updateMyHonesty(...args) { - const buildUpdate = body => _.pick(body, 'isHonest'); - const validate = ({ isHonest }) => isHonest === true; - createUpdateUserProperties( - buildUpdate, - validate, - 'buttons.accepted-honesty' - )(...args); -} - -function updateMyQuincyEmail(...args) { - const buildUpdate = body => _.pick(body, 'sendQuincyEmail'); - const validate = ({ sendQuincyEmail }) => - typeof sendQuincyEmail === 'boolean'; - createUpdateUserProperties( - buildUpdate, - validate, - 'flash.subscribe-to-quincy-updated' - )(...args); -} - -export function updateMyClassroomMode(...args) { - const buildUpdate = body => _.pick(body, 'isClassroomAccount'); - const validate = ({ isClassroomAccount }) => - typeof isClassroomAccount === 'boolean'; - createUpdateUserProperties( - buildUpdate, - validate, - 'flash.classroom-mode-updated' - )(...args); -} - -function handleInvalidUpdate(res) { - res.status(403).json({ - type: 'danger', - message: 'flash.wrong-updating' - }); -} - -function updateUserFlag(req, res, next) { - const { user, body: update } = req; - const allowedKeys = ['githubProfile', 'linkedin', 'twitter', 'website']; - if (Object.keys(update).every(key => allowedKeys.includes(key))) { - return user.updateAttributes( - update, - createStandardHandler(req, res, next, 'flash.updated-socials') - ); - } - return res.status(403).json({ - type: 'danger', - message: 'flash.invalid-update-flag' - }); -} diff --git a/api-server/src/server/boot/status.js b/api-server/src/server/boot/status.js deleted file mode 100644 index 6ac058c6213..00000000000 --- a/api-server/src/server/boot/status.js +++ /dev/null @@ -1,6 +0,0 @@ -export default function bootStatus(app) { - const api = app.loopback.Router(); - - api.get('/status/ping', (req, res) => res.json({ msg: 'pong' })); - app.use(api); -} diff --git a/api-server/src/server/boot/t-wiki.js b/api-server/src/server/boot/t-wiki.js deleted file mode 100644 index b559b47759b..00000000000 --- a/api-server/src/server/boot/t-wiki.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = function (app) { - var router = app.loopback.Router(); - router.get('/wiki/*', showForum); - - app.use(router); - - function showForum(req, res) { - res.redirect('http://forum.freecodecamp.org/'); - } -}; diff --git a/api-server/src/server/boot/user.js b/api-server/src/server/boot/user.js deleted file mode 100644 index 70288aae553..00000000000 --- a/api-server/src/server/boot/user.js +++ /dev/null @@ -1,535 +0,0 @@ -import debugFactory from 'debug'; -import dedent from 'dedent'; -import { body } from 'express-validator'; -import { pick } from 'lodash'; -import fetch from 'node-fetch'; - -import { - fixCompletedChallengeItem, - fixCompletedExamItem, - fixCompletedSurveyItem, - fixPartiallyCompletedChallengeItem, - fixSavedChallengeItem -} from '../../common/utils'; -import { removeCookies } from '../utils/getSetAccessToken'; -import { ifNoUser401, ifNoUserRedirectHome } from '../utils/middleware'; -import { - getProgress, - normaliseUserFields, - userPropsForSession -} from '../utils/publicUserProps'; -import { getRedirectParams } from '../utils/redirection'; -import { trimTags } from '../utils/validators'; -import { - createDeleteUserToken, - encodeUserToken -} from '../middlewares/user-token'; -import { createDeleteMsUsername } from '../middlewares/ms-username'; -import { validateSurvey, createDeleteUserSurveys } from '../middlewares/survey'; -import { deprecatedEndpoint } from '../utils/disabled-endpoints'; - -const log = debugFactory('fcc:boot:user'); -const sendNonUserToHome = ifNoUserRedirectHome(); - -function bootUser(app) { - const api = app.loopback.Router(); - - const getSessionUser = createReadSessionUser(app); - const postReportUserProfile = createPostReportUserProfile(app); - const postDeleteAccount = createPostDeleteAccount(app); - const postUserToken = createPostUserToken(app); - const deleteUserToken = createDeleteUserToken(app); - const postMsUsername = createPostMsUsername(app); - const deleteMsUsername = createDeleteMsUsername(app); - const postSubmitSurvey = createPostSubmitSurvey(app); - const deleteUserSurveys = createDeleteUserSurveys(app); - api.get('/account', sendNonUserToHome, deprecatedEndpoint); - api.get('/account/unlink/:social', sendNonUserToHome, getUnlinkSocial); - api.get('/user/get-session-user', getSessionUser); - api.post( - '/account/delete', - ifNoUser401, - deleteUserToken, - deleteMsUsername, - deleteUserSurveys, - postDeleteAccount - ); - api.post( - '/account/reset-progress', - ifNoUser401, - deleteUserToken, - deleteMsUsername, - deleteUserSurveys, - postResetProgress - ); - api.post( - '/user/report-user/', - ifNoUser401, - body('reportDescription').customSanitizer(trimTags), - postReportUserProfile - ); - - api.post('/user/user-token', ifNoUser401, postUserToken); - api.delete( - '/user/user-token', - ifNoUser401, - deleteUserToken, - deleteUserTokenResponse - ); - - api.post('/user/ms-username', ifNoUser401, postMsUsername); - api.delete( - '/user/ms-username', - ifNoUser401, - deleteMsUsername, - deleteMsUsernameResponse - ); - - api.post( - '/user/submit-survey', - ifNoUser401, - validateSurvey, - postSubmitSurvey - ); - - app.use(api); -} - -function createPostUserToken(app) { - const { UserToken } = app.models; - - return async function postUserToken(req, res) { - const ttl = 900 * 24 * 60 * 60 * 1000; - let encodedUserToken; - - try { - await UserToken.destroyAll({ userId: req.user.id }); - const newUserToken = await UserToken.create({ ttl, userId: req.user.id }); - - if (!newUserToken?.id) throw new Error(); - encodedUserToken = encodeUserToken(newUserToken.id); - } catch (e) { - return res.status(500).send('Error starting project'); - } - - return res.json({ userToken: encodedUserToken }); - }; -} - -function deleteUserTokenResponse(req, res) { - if (!req.userTokenDeleted) { - return res.status(500).send('Error deleting user token'); - } - - return res.send({ userToken: null }); -} - -export const getMsTranscriptApiUrl = msTranscript => { - // example msTranscriptUrl: https://learn.microsoft.com/en-us/users/mot01/transcript/8u6awert43q1plo - const url = new URL(msTranscript); - - const transcriptUrlRegex = /\/transcript\/([^/]+)\/?/; - const id = transcriptUrlRegex.exec(url.pathname)?.[1]; - return `https://learn.microsoft.com/api/profiles/transcript/share/${ - id ?? '' - }`; -}; - -function createPostMsUsername(app) { - const { MsUsername } = app.models; - - return async function postMsUsername(req, res) { - // example msTranscriptUrl: https://learn.microsoft.com/en-us/users/mot01/transcript/8u6awert43q1plo - // the last part is the transcript ID - // the username is irrelevant, and retrieved from the MS API response - - const { msTranscriptUrl } = req.body; - - if (!msTranscriptUrl) { - return res.status(400).json({ - type: 'error', - message: 'flash.ms.transcript.link-err-1' - }); - } - - const msTranscriptApiUrl = getMsTranscriptApiUrl(msTranscriptUrl); - - try { - const msApiRes = await fetch(msTranscriptApiUrl); - - if (!msApiRes.ok) { - return res - .status(404) - .json({ type: 'error', message: 'flash.ms.transcript.link-err-2' }); - } - - const { userName } = await msApiRes.json(); - - if (!userName) { - return res - .status(500) - .json({ type: 'error', message: 'flash.ms.transcript.link-err-3' }); - } - - // Don't create if username is used by another fCC account - const usernameUsed = await MsUsername.findOne({ - where: { msUsername: userName } - }); - - if (usernameUsed) { - return res - .status(403) - .json({ type: 'error', message: 'flash.ms.transcript.link-err-4' }); - } - - await MsUsername.destroyAll({ userId: req.user.id }); - - const ttl = 900 * 24 * 60 * 60 * 1000; - const newMsUsername = await MsUsername.create({ - ttl, - userId: req.user.id, - msUsername: userName - }); - - if (!newMsUsername?.id) { - return res - .status(500) - .json({ type: 'error', message: 'flash.ms.transcript.link-err-5' }); - } - - return res.json({ msUsername: userName }); - } catch (e) { - log(e); - return res - .status(500) - .json({ type: 'error', message: 'flash.ms.transcript.link-err-6' }); - } - }; -} - -function deleteMsUsernameResponse(req, res) { - if (!req.msUsernameDeleted) { - return res.status(500).json({ - type: 'error', - message: 'flash.ms.transcript.unlink-err' - }); - } - - return res.send({ msUsername: null }); -} - -function createPostSubmitSurvey(app) { - const { Survey } = app.models; - - return async function postSubmitSurvey(req, res) { - const { user, body } = req; - const { surveyResults } = body; - const { id: userId } = user; - const { title } = surveyResults; - - const completedSurveys = await Survey.find({ - where: { userId } - }); - - const surveyAlreadyTaken = completedSurveys.some(s => s.title === title); - if (surveyAlreadyTaken) { - return res.status(400).json({ - type: 'error', - message: 'flash.survey.err-2' - }); - } - - try { - const newSurvey = { - ...surveyResults, - userId: user.id - }; - - const createdSurvey = await Survey.create(newSurvey); - if (!createdSurvey) { - throw new Error('Error creating survey'); - } - - return res.json({ - type: 'success', - message: 'flash.survey.success' - }); - } catch (e) { - log(e); - return res.status(500).json({ - type: 'error', - message: 'flash.survey.err-3' - }); - } - }; -} - -function createReadSessionUser(app) { - const { MsUsername, Survey, UserToken } = app.models; - - return async function getSessionUser(req, res, next) { - const queryUser = req.user; - - let encodedUserToken; - try { - const userId = queryUser?.id; - const userToken = userId - ? await UserToken.findOne({ - where: { userId } - }) - : null; - - encodedUserToken = userToken ? encodeUserToken(userToken.id) : undefined; - } catch (e) { - return next(e); - } - - let msUsername; - try { - const userId = queryUser?.id; - const msUser = userId - ? await MsUsername.findOne({ - where: { userId } - }) - : null; - - msUsername = msUser ? msUser.msUsername : undefined; - } catch (e) { - return next(e); - } - - let completedSurveys; - try { - const userId = queryUser?.id; - completedSurveys = userId - ? await Survey.find({ - where: { userId } - }) - : []; - } catch (e) { - return next(e); - } - - if (!queryUser || !queryUser.toJSON().username) { - // TODO: This should return an error status - return res.json({ user: {}, result: '' }); - } - - try { - const [ - completedChallenges, - completedExams, - partiallyCompletedChallenges, - progressTimestamps, - savedChallenges - ] = await Promise.all( - [ - queryUser.getCompletedChallenges$(), - queryUser.getCompletedExams$(), - queryUser.getPartiallyCompletedChallenges$(), - queryUser.getPoints$(), - queryUser.getSavedChallenges$() - ].map(obs => obs.toPromise()) - ); - - const { calendar } = getProgress(progressTimestamps); - const user = { - ...queryUser.toJSON(), - calendar, - completedChallenges: completedChallenges.map(fixCompletedChallengeItem), - completedExams: completedExams.map(fixCompletedExamItem), - partiallyCompletedChallenges: partiallyCompletedChallenges.map( - fixPartiallyCompletedChallengeItem - ), - savedChallenges: savedChallenges.map(fixSavedChallengeItem) - }; - const response = { - user: { - [user.username]: { - ...pick(user, userPropsForSession), - username: user.usernameDisplay || user.username, - isEmailVerified: !!user.emailVerified, - ...normaliseUserFields(user), - joinDate: user.id.getTimestamp(), - userToken: encodedUserToken, - msUsername, - completedSurveys: completedSurveys.map(fixCompletedSurveyItem) - } - }, - result: user.username - }; - return res.json(response); - } catch (e) { - // TODO: This should return an error status - return res.json({ user: {}, result: '' }); - } - }; -} - -function getUnlinkSocial(req, res, next) { - const { user } = req; - const { username } = user; - const { origin } = getRedirectParams(req); - let social = req.params.social; - if (!social) { - req.flash('danger', 'No social account found'); - return res.redirect('/' + username); - } - - social = social.toLowerCase(); - const validSocialAccounts = ['twitter', 'linkedin']; - if (validSocialAccounts.indexOf(social) === -1) { - req.flash('danger', 'Invalid social account'); - return res.redirect('/' + username); - } - - if (!user[social]) { - req.flash('danger', `No ${social} account associated`); - return res.redirect('/' + username); - } - - const query = { - where: { - provider: social - } - }; - - return user.identities(query, function (err, identities) { - if (err) { - return next(err); - } - - // assumed user identity is unique by provider - let identity = identities.shift(); - if (!identity) { - req.flash('danger', 'No social account found'); - return res.redirect('/' + username); - } - - return identity.destroy(function (err) { - if (err) { - return next(err); - } - - const updateData = { [social]: null }; - - return user.updateAttributes(updateData, err => { - if (err) { - return next(err); - } - log(`${social} has been unlinked successfully`); - - req.flash('info', `You've successfully unlinked your ${social}.`); - return res.redirectWithFlash(`${origin}/${username}`); - }); - }); - }); -} - -function postResetProgress(req, res, next) { - const { user } = req; - return user.updateAttributes( - { - progressTimestamps: [Date.now()], - currentChallengeId: '', - isRespWebDesignCert: false, - is2018DataVisCert: false, - isFrontEndLibsCert: false, - isJsAlgoDataStructCert: false, - isApisMicroservicesCert: false, - isInfosecQaCert: false, - isQaCertV7: false, - isInfosecCertV7: false, - is2018FullStackCert: false, - isFrontEndCert: false, - isBackEndCert: false, - isDataVisCert: false, - isFullStackCert: false, - isSciCompPyCertV7: false, - isDataAnalysisPyCertV7: false, - isMachineLearningPyCertV7: false, - isRelationalDatabaseCertV8: false, - isCollegeAlgebraPyCertV8: false, - isFoundationalCSharpCertV8: false, - isJsAlgoDataStructCertV8: false, - completedChallenges: [], - completedExams: [], - savedChallenges: [], - partiallyCompletedChallenges: [], - needsModeration: false - }, - function (err) { - if (err) { - return next(err); - } - return res.status(200).json({}); - } - ); -} - -function createPostDeleteAccount(app) { - const { User } = app.models; - return async function postDeleteAccount(req, res, next) { - return User.destroyById(req.user.id, function (err) { - if (err) { - return next(err); - } - req.logout(); - removeCookies(req, res); - return res.status(200).json({}); - }); - }; -} - -function createPostReportUserProfile(app) { - const { Email } = app.models; - return function postReportUserProfile(req, res, next) { - const { user } = req; - const { username, reportDescription: report } = req.body; - const { origin } = getRedirectParams(req); - log(username); - log(report); - - if (!username || !report || report === '') { - return res.json({ - type: 'danger', - message: 'flash.provide-username' - }); - } - return Email.send$( - { - type: 'email', - to: 'support@freecodecamp.org', - cc: user.email, - from: 'team@freecodecamp.org', - subject: `Abuse Report : Reporting ${username}'s profile.`, - text: dedent(` - Hello Team,\n - This is to report the profile of ${username}.\n - Report Details:\n - ${report}\n\n - Reported by: - Username: ${user.username} - Name: ${user.name} - Email: ${user.email}\n - Thanks and regards, - ${user.name} - `) - }, - err => { - if (err) { - err.redirectTo = `${origin}/${username}`; - return next(err); - } - - return res.json({ - type: 'info', - message: 'flash.report-sent', - variables: { email: user.email } - }); - } - ); - }; -} - -export default bootUser; diff --git a/api-server/src/server/boot/z-not-found.js b/api-server/src/server/boot/z-not-found.js deleted file mode 100644 index 1c84138388f..00000000000 --- a/api-server/src/server/boot/z-not-found.js +++ /dev/null @@ -1,19 +0,0 @@ -import accepts from 'accepts'; -import { getRedirectParams } from '../utils/redirection'; - -export default function fourOhFour(app) { - app.all('*', function (req, res) { - const accept = accepts(req); - // prioritise returning json - const type = accept.type('json', 'html', 'text'); - const { path } = req; - const { origin } = getRedirectParams(req); - - if (type === 'json') { - return res.status('404').json({ error: 'path not found' }); - } else { - req.flash('danger', `We couldn't find path ${path}`); - return res.redirectWithFlash(`${origin}/404`); - } - }); -} diff --git a/api-server/src/server/boot_tests/README.md b/api-server/src/server/boot_tests/README.md deleted file mode 100644 index 618b1130997..00000000000 --- a/api-server/src/server/boot_tests/README.md +++ /dev/null @@ -1,3 +0,0 @@ -## Test scripts for the boot directory - -These files cannot be co-located with the files under test due to the auto-discovery the loopback-boot employs. diff --git a/api-server/src/server/boot_tests/certificate.test.js b/api-server/src/server/boot_tests/certificate.test.js deleted file mode 100644 index 556696be76c..00000000000 --- a/api-server/src/server/boot_tests/certificate.test.js +++ /dev/null @@ -1,94 +0,0 @@ -import { - getFallbackFullStackDate, - ifNoCertification404 -} from '../boot/certificate'; -import { fullStackChallenges } from './fixtures'; - -export const mockReq = opts => { - const req = {}; - return { ...req, ...opts }; -}; - -export const mockRes = opts => { - const res = {}; - res.status = jest.fn().mockReturnValue(res); - res.end = jest.fn().mockReturnValue(res); - res.json = jest.fn().mockReturnValue(res); - res.redirect = jest.fn().mockReturnValue(res); - res.set = jest.fn().mockReturnValue(res); - res.clearCookie = jest.fn().mockReturnValue(res); - res.cookie = jest.fn().mockReturnValue(res); - return { ...res, ...opts }; -}; - -describe('boot/certificate', () => { - describe('getFallbackFullStackDate', () => { - it('should return the date of the latest completed challenge', () => { - expect(getFallbackFullStackDate(fullStackChallenges)).toBe(1685210952511); - }); - }); - - describe('ifNoCertification404', () => { - it('declares a 404 when there is no certSlug in the body', () => { - const req = mockReq({ - body: {} - }); - const res = mockRes(); - const next = jest.fn(); - - ifNoCertification404(req, res, next); - - expect(res.status).toHaveBeenCalledWith(404); - expect(next).not.toHaveBeenCalled(); - }); - - it('declares a 404 for an invalid certSlug in the body', () => { - const req = mockReq({ - body: { certSlug: 'not-a-real-certSlug' } - }); - const res = mockRes(); - const next = jest.fn(); - - ifNoCertification404(req, res, next); - - expect(res.status).toHaveBeenCalledWith(404); - expect(next).not.toHaveBeenCalled(); - }); - - it('calls next for a valid certSlug of a current certification', () => { - const req = mockReq({ - body: { certSlug: 'responsive-web-design' } - }); - const res = mockRes(); - const next = jest.fn(); - - ifNoCertification404(req, res, next); - - expect(next).toHaveBeenCalled(); - }); - - it('calls next for a valid certSlug of a legacy certification', () => { - const req = mockReq({ - body: { certSlug: 'legacy-front-end' } - }); - const res = mockRes(); - const next = jest.fn(); - - ifNoCertification404(req, res, next); - - expect(next).toHaveBeenCalled(); - }); - - it('calls next for a valid certSlug of the legacy full stack certification', () => { - const req = mockReq({ - body: { certSlug: 'full-stack' } - }); - const res = mockRes(); - const next = jest.fn(); - - ifNoCertification404(req, res, next); - - expect(next).toHaveBeenCalled(); - }); - }); -}); diff --git a/api-server/src/server/boot_tests/challenge.test.js b/api-server/src/server/boot_tests/challenge.test.js deleted file mode 100644 index 16afd62b50c..00000000000 --- a/api-server/src/server/boot_tests/challenge.test.js +++ /dev/null @@ -1,447 +0,0 @@ -import { find } from 'lodash'; - -import { - buildUserUpdate, - buildExamUserUpdate, - buildChallengeUrl, - createChallengeUrlResolver, - createRedirectToCurrentChallenge, - getFirstChallenge, - isValidChallengeCompletion -} from '../boot/challenge'; - -import { - firstChallengeUrl, - requestedChallengeUrl, - mockAllChallenges, - mockChallenge, - mockUser, - mockUser2, - mockGetFirstChallenge, - mockCompletedChallenge, - mockCompletedChallengeNoFiles, - mockCompletedChallenges, - mockFailingExamChallenge, - mockPassingExamChallenge, - mockBetterPassingExamChallenge, - mockWorsePassingExamChallenge -} from './fixtures'; - -export const mockReq = opts => { - const req = {}; - return { ...req, ...opts }; -}; - -export const mockRes = opts => { - const res = {}; - res.status = jest.fn().mockReturnValue(res); - res.json = jest.fn().mockReturnValue(res); - res.redirect = jest.fn().mockReturnValue(res); - res.set = jest.fn().mockReturnValue(res); - res.clearCookie = jest.fn().mockReturnValue(res); - res.cookie = jest.fn().mockReturnValue(res); - return { ...res, ...opts }; -}; - -describe('boot/challenge', () => { - xdescribe('backendChallengeCompleted', () => {}); - - describe('buildUserUpdate', () => { - it('returns an Object with a nested "completedChallenges" property', () => { - const result = buildUserUpdate( - mockUser, - '123abc', - mockCompletedChallenge, - 'UTC' - ); - expect(result).toHaveProperty('updateData.$push.completedChallenges'); - }); - - it('preserves file contents if the completed challenge is a JS Project', () => { - const jsChallengeId = 'aa2e6f85cab2ab736c9a9b24'; - const completedChallenge = { - ...mockCompletedChallenge, - completedDate: Date.now(), - id: jsChallengeId - }; - const result = buildUserUpdate( - mockUser, - jsChallengeId, - completedChallenge, - 'UTC' - ); - const newCompletedChallenge = result.updateData.$push.completedChallenges; - - expect(newCompletedChallenge).toEqual(completedChallenge); - }); - - it('preserves the original completed date of a challenge', () => { - const completedChallengeId = 'aaa48de84e1ecc7c742e1124'; - const completedChallenge = { - ...mockCompletedChallenge, - completedDate: Date.now(), - id: completedChallengeId - }; - const originalCompletion = find( - mockCompletedChallenges, - x => x.id === completedChallengeId - ).completedDate; - const result = buildUserUpdate( - mockUser, - completedChallengeId, - completedChallenge, - 'UTC' - ); - - const updatedCompletedChallenge = - result.updateData.$set['completedChallenges.2']; - - expect(updatedCompletedChallenge.completedDate).toEqual( - originalCompletion - ); - }); - - it('does not attempt to update progressTimestamps for a previously completed challenge', () => { - const completedChallengeId = 'aaa48de84e1ecc7c742e1124'; - const completedChallenge = { - ...mockCompletedChallenge, - completedDate: Date.now(), - id: completedChallengeId - }; - const { updateData } = buildUserUpdate( - mockUser, - completedChallengeId, - completedChallenge, - 'UTC' - ); - - const hasProgressTimestamps = - '$push' in updateData && 'progressTimestamps' in updateData.$push; - expect(hasProgressTimestamps).toBe(false); - }); - - it('provides a progressTimestamps update for new challenge completion', () => { - expect.assertions(2); - const { updateData } = buildUserUpdate( - mockUser, - '123abc', - mockCompletedChallenge, - 'UTC' - ); - expect(updateData).toHaveProperty('$push'); - expect(updateData.$push).toHaveProperty('progressTimestamps'); - }); - - it('will $push newly completed challenges to the completedChallenges array', () => { - const { - updateData: { - $push: { completedChallenges } - } - } = buildUserUpdate( - mockUser, - '123abc', - mockCompletedChallengeNoFiles, - 'UTC' - ); - - expect(completedChallenges).toEqual(mockCompletedChallengeNoFiles); - }); - }); - - describe('buildExamUserUpdate', () => { - it('should $push exam results to completedExams[]', () => { - const { - updateData: { - $push: { completedExams } - } - } = buildExamUserUpdate(mockUser, mockFailingExamChallenge); - expect(completedExams).toEqual(mockFailingExamChallenge); - }); - - it('should not add failing exams to completedChallenges[]', () => { - const { alreadyCompleted, addPoint, updateData } = buildExamUserUpdate( - mockUser, - mockFailingExamChallenge - ); - - expect(updateData).not.toHaveProperty('$push.completedChallenges'); - expect(updateData).not.toHaveProperty('$set.completedChallenges'); - expect(addPoint).toBe(false); - expect(alreadyCompleted).toBe(false); - }); - - it('should $push newly passed exams to completedChallenge[]', () => { - const { - alreadyCompleted, - addPoint, - updateData: { - $push: { completedChallenges } - } - } = buildExamUserUpdate(mockUser, mockPassingExamChallenge); - - expect(completedChallenges).toEqual(mockPassingExamChallenge); - expect(addPoint).toBe(true); - expect(alreadyCompleted).toBe(false); - }); - - it('should not update passed exams with worse results in completedChallenge[]', () => { - const { alreadyCompleted, addPoint, updateData } = buildExamUserUpdate( - mockUser2, - mockWorsePassingExamChallenge - ); - - expect(updateData).not.toHaveProperty('$push.completedChallenges'); - expect(updateData).not.toHaveProperty('$set.completedChallenges'); - expect(addPoint).toBe(false); - expect(alreadyCompleted).toBe(true); - }); - - it('should update passed exams with better results in completedChallenge[]', () => { - const { - alreadyCompleted, - addPoint, - completedDate, - updateData: { $set } - } = buildExamUserUpdate(mockUser2, mockBetterPassingExamChallenge); - - expect($set['completedChallenges.4'].examResults).toEqual( - mockBetterPassingExamChallenge.examResults - ); - expect(addPoint).toBe(false); - expect(alreadyCompleted).toBe(true); - expect(completedDate).toBe(1538052380328); - }); - }); - - describe('buildChallengeUrl', () => { - it('resolves the correct Url for the provided challenge', () => { - const result = buildChallengeUrl(mockChallenge); - - expect(result).toEqual(requestedChallengeUrl); - }); - }); - - describe('challengeUrlResolver', () => { - it('resolves to the first challenge url by default', async () => { - const challengeUrlResolver = await createChallengeUrlResolver( - mockAllChallenges, - { - _getFirstChallenge: mockGetFirstChallenge - } - ); - - return challengeUrlResolver().then(url => { - expect(url).toEqual(firstChallengeUrl); - }); - }, 10000); - - it('returns the first challenge url if the provided id does not relate to a challenge', async () => { - const challengeUrlResolver = await createChallengeUrlResolver( - mockAllChallenges, - { - _getFirstChallenge: mockGetFirstChallenge - } - ); - - return challengeUrlResolver('not-a-real-challenge').then(url => { - expect(url).toEqual(firstChallengeUrl); - }); - }); - - it('resolves the correct url for the requested challenge', async () => { - const challengeUrlResolver = await createChallengeUrlResolver( - mockAllChallenges, - { - _getFirstChallenge: mockGetFirstChallenge - } - ); - - return challengeUrlResolver('123abc').then(url => { - expect(url).toEqual(requestedChallengeUrl); - }); - }); - }); - - describe('getFirstChallenge', () => { - it('returns the correct challenge url from the model', async () => { - const result = await getFirstChallenge(mockAllChallenges); - - expect(result).toEqual(firstChallengeUrl); - }); - - it('returns the learn base if no challenges found', async () => { - const result = await getFirstChallenge([]); - - expect(result).toEqual('/learn'); - }); - }); - - describe('isValidChallengeCompletion', () => { - const validObjectId = '5c716d1801013c3ce3aa23e6'; - - it('declares a 403 for an invalid id in the body', () => { - expect.assertions(2); - const req = mockReq({ - body: { id: 'not-a-real-id' } - }); - const res = mockRes(); - const next = jest.fn(); - - isValidChallengeCompletion(req, res, next); - - expect(res.status).toHaveBeenCalledWith(403); - expect(next).not.toHaveBeenCalled(); - }); - - it('declares a 403 for an invalid challengeType in the body', () => { - expect.assertions(2); - const req = mockReq({ - body: { id: validObjectId, challengeType: 'ponyfoo' } - }); - const res = mockRes(); - const next = jest.fn(); - - isValidChallengeCompletion(req, res, next); - - expect(res.status).toHaveBeenCalledWith(403); - expect(next).not.toHaveBeenCalled(); - }); - - it('declares a 403 for an invalid solution in the body', () => { - expect.assertions(2); - const req = mockReq({ - body: { - id: validObjectId, - challengeType: '1', - solution: 'https://not-a-url' - } - }); - const res = mockRes(); - const next = jest.fn(); - - isValidChallengeCompletion(req, res, next); - - expect(res.status).toHaveBeenCalledWith(403); - expect(next).not.toHaveBeenCalled(); - }); - - it('calls next if the body is valid', () => { - const req = mockReq({ - body: { - id: validObjectId, - challengeType: '1', - solution: 'https://www.freecodecamp.org' - } - }); - const res = mockRes(); - const next = jest.fn(); - - isValidChallengeCompletion(req, res, next); - - expect(next).toHaveBeenCalled(); - }); - - it('calls next if only the id is provided', () => { - const req = mockReq({ - body: { - id: validObjectId - } - }); - const res = mockRes(); - const next = jest.fn(); - - isValidChallengeCompletion(req, res, next); - - expect(next).toHaveBeenCalled(); - }); - - it('can handle an "int" challengeType', () => { - const req = mockReq({ - body: { - id: validObjectId, - challengeType: 1 - } - }); - const res = mockRes(); - const next = jest.fn(); - - isValidChallengeCompletion(req, res, next); - - expect(next).toHaveBeenCalled(); - }); - }); - - xdescribe('modernChallengeCompleted', () => {}); - - xdescribe('projectCompleted', () => {}); - - describe('redirectToCurrentChallenge', () => { - const mockHomeLocation = 'https://www.example.com'; - const mockLearnUrl = `${mockHomeLocation}/learn`; - const mockgetParamsFromReq = () => ({ - returnTo: mockLearnUrl, - origin: mockHomeLocation, - pathPrefix: '' - }); - const mockNormalizeParams = params => params; - - it('redirects to the learn base url for non-users', async () => { - const redirectToCurrentChallenge = createRedirectToCurrentChallenge( - () => {}, - mockNormalizeParams, - mockgetParamsFromReq - ); - const req = mockReq(); - const res = mockRes(); - const next = jest.fn(); - await redirectToCurrentChallenge(req, res, next); - - expect(res.redirect).toHaveBeenCalledWith(mockLearnUrl); - }); - - it('redirects to the url provided by the challengeUrlResolver', async () => { - const challengeUrlResolver = await createChallengeUrlResolver( - mockAllChallenges, - { - _getFirstChallenge: mockGetFirstChallenge - } - ); - const expectedUrl = `${mockHomeLocation}${requestedChallengeUrl}`; - const redirectToCurrentChallenge = createRedirectToCurrentChallenge( - challengeUrlResolver, - mockNormalizeParams, - mockgetParamsFromReq - ); - const req = mockReq({ - user: mockUser - }); - const res = mockRes(); - const next = jest.fn(); - await redirectToCurrentChallenge(req, res, next); - - expect(res.redirect).toHaveBeenCalledWith(expectedUrl); - }); - - it('redirects to the first challenge for users without a currentChallengeId', async () => { - const challengeUrlResolver = await createChallengeUrlResolver( - mockAllChallenges, - { - _getFirstChallenge: mockGetFirstChallenge - } - ); - const redirectToCurrentChallenge = createRedirectToCurrentChallenge( - challengeUrlResolver, - mockNormalizeParams, - mockgetParamsFromReq - ); - const req = mockReq({ - user: { ...mockUser, currentChallengeId: '' } - }); - const res = mockRes(); - const next = jest.fn(); - await redirectToCurrentChallenge(req, res, next); - const expectedUrl = `${mockHomeLocation}${firstChallengeUrl}`; - expect(res.redirect).toHaveBeenCalledWith(expectedUrl); - }); - }); -}); diff --git a/api-server/src/server/boot_tests/fixtures.js b/api-server/src/server/boot_tests/fixtures.js deleted file mode 100644 index 07f85c3f242..00000000000 --- a/api-server/src/server/boot_tests/fixtures.js +++ /dev/null @@ -1,266 +0,0 @@ -import { isEqual } from 'lodash'; -import { isEmail } from 'validator'; - -export const firstChallengeUrl = '/learn/the/first/challenge'; -export const requestedChallengeUrl = '/learn/my/actual/challenge'; - -export const mockChallenge = { - id: '123abc', - block: 'actual', - superBlock: 'my', - dashedName: 'challenge' -}; - -export const mockFirstChallenge = { - id: '456def', - block: 'first', - superBlock: 'the', - dashedName: 'challenge', - challengeOrder: 0, - superOrder: 0, - order: 0 -}; - -export const mockCompletedChallenge = { - id: '890xyz', - challengeType: 0, - files: [ - { - contents: 'file contents', - key: 'indexfile', - name: 'index', - path: 'index.file', - ext: 'file' - } - ], - completedDate: Date.now() -}; - -export const mockCompletedChallengeNoFiles = { - id: '123abc456def', - challengeType: 0, - completedDate: Date.now() -}; - -export const mockFailingExamChallenge = { - id: '647e22d18acb466c97ccbef8', - challengeType: 17, - completedDate: Date.now(), - examResults: { - "numberOfCorrectAnswers" : 5, - "numberOfQuestionsInExam" : 10, - "percentCorrect" : 50, - "passingPercent" : 70, - "passed" : false, - "examTimeInSeconds" : 1200 - } -} - -export const mockPassingExamChallenge = { - id: '647e22d18acb466c97ccbef8', - challengeType: 17, - completedDate: 1538052380328, - examResults: { - "numberOfCorrectAnswers" : 9, - "numberOfQuestionsInExam" : 10, - "percentCorrect" : 90, - "passingPercent" : 70, - "passed" : true, - "examTimeInSeconds" : 1200 - } -} - -export const mockBetterPassingExamChallenge = { - id: '647e22d18acb466c97ccbef8', - challengeType: 17, - completedDate: Date.now(), - examResults: { - "numberOfCorrectAnswers" : 10, - "numberOfQuestionsInExam" : 10, - "percentCorrect" : 100, - "passingPercent" : 70, - "passed" : true, - "examTimeInSeconds" : 1200 - } -} - -export const mockWorsePassingExamChallenge = { - id: '647e22d18acb466c97ccbef8', - challengeType: 17, - completedDate: Date.now(), - examResults: { - "numberOfCorrectAnswers" : 8, - "numberOfQuestionsInExam" : 10, - "percentCorrect" : 80, - "passingPercent" : 70, - "passed" : true, - "examTimeInSeconds" : 1200 - } -} - -export const mockCompletedChallenges = [ - { - id: 'bd7123c8c441eddfaeb5bdef', - completedDate: 1538052380328.0 - }, - { - id: '587d7dbd367417b2b2512bb4', - completedDate: 1547472893032.0, - files: [] - }, - { - id: 'aaa48de84e1ecc7c742e1124', - completedDate: 1541678430790.0, - files: [ - { - contents: "function palindrome(str) {\n const clean = str.replace(/[\\W_]/g, '').toLowerCase()\n const revStr = clean.split('').reverse().join('');\n return clean === revStr;\n}\n\n\n\npalindrome(\"eye\");\n", - ext: 'js', - path: 'index.js', - name: 'index', - key: 'indexjs' - } - ] - }, - { - id: '5a24c314108439a4d4036164', - completedDate: 1543845124143.0, - files: [] - } -]; -export const mockUserID = '5c7d892aff9777c8b1c1a95e'; - -export const createUserMockFn = jest.fn(); -export const createDonationMockFn = jest.fn(); -export const updateDonationAttr = jest.fn(); -export const updateUserAttr = jest.fn(); -export const mockUser = { - id: mockUserID, - username: 'camperbot', - currentChallengeId: '123abc', - email: 'donor@freecodecamp.com', - timezone: 'UTC', - completedChallenges: mockCompletedChallenges, - progressTimestamps: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], - isDonating: true, - donationEmails: ['donor@freecodecamp.com', 'donor@freecodecamp.com'], - createDonation: donation => { - createDonationMockFn(donation); - return mockObservable; - }, - updateAttributes: updateUserAttr -}; - -export const mockUser2 = JSON.parse(JSON.stringify(mockUser)); -mockUser2.completedChallenges.push(mockPassingExamChallenge); - -const mockObservable = { - toPromise: () => Promise.resolve('result') -}; - -export const mockDonation = { - id: '5e5f8eda5ed7be2b54e18718', - email: 'donor@freecodecamp.com', - provider: 'paypal', - amount: 500, - duration: 'month', - startDate: { - _when: '2018-11-01T00:00:00.000Z', - _date: '2018-11-01T00:00:00.000Z' - }, - subscriptionId: 'I-BA1ATBNF8T3P', - userId: mockUserID, - updateAttributes: updateDonationAttr -}; - -export function createNewUserFromEmail(email) { - const newMockUser = mockUser; - newMockUser.email = email; - newMockUser.username = 'camberbot2'; - newMockUser.ID = '5c7d892aff9888c8b1c1a95e'; - return newMockUser; -} - -export const mockApp = { - models: { - Donation: { - findOne(query, cb) { - return isEqual(query, matchSubscriptionIdQuery) - ? cb(null, mockDonation) - : cb(Error('No Donation')); - } - }, - User: { - findById(id, cb) { - if (id === mockUser.id) { - return cb(null, mockUser); - } - return cb(Error('No user')); - }, - findOne(query, cb) { - if (isEqual(query, matchEmailQuery) || isEqual(query, matchUserIdQuery)) - return cb(null, mockUser); - return cb(null, null); - }, - create(query, cb) { - if (!isEmail(query.email)) return cb(new Error('email not valid')); - else if (query.email === mockUser.email) - return cb(new Error('user exist')); - createUserMockFn(); - return Promise.resolve(createNewUserFromEmail(query.email)); - } - } - } -}; - -export const mockAllChallenges = [mockFirstChallenge, mockChallenge]; - -export const mockGetFirstChallenge = () => firstChallengeUrl; - -export const matchEmailQuery = { - where: { email: mockUser.email } -}; -export const matchSubscriptionIdQuery = { - where: { subscriptionId: mockDonation.subscriptionId } -}; -export const matchUserIdQuery = { - where: { id: mockUser.id } -}; - -export const fullStackChallenges = [ - { - completedDate: 1585210952511, - id: '5a553ca864b52e1d8bceea14', - challengeType: 7, - files: [] - }, - { - completedDate: 1585210952511, - id: '561add10cb82ac38a17513bc', - challengeType: 7, - files: [] - }, - { - completedDate: 1588665778679, - id: '561acd10cb82ac38a17513bc', - challengeType: 7, - files: [] - }, - { - completedDate: 1685210952511, - id: '561abd10cb81ac38a17513bc', - challengeType: 7, - files: [] - }, - { - completedDate: 1585210952511, - id: '561add10cb82ac38a17523bc', - challengeType: 7, - files: [] - }, - { - completedDate: 1588665778679, - id: '561add10cb82ac38a17213bc', - challengeType: 7, - files: [] - } -]; diff --git a/api-server/src/server/boot_tests/settings.test.js b/api-server/src/server/boot_tests/settings.test.js deleted file mode 100644 index 4e2cf80c612..00000000000 --- a/api-server/src/server/boot_tests/settings.test.js +++ /dev/null @@ -1,175 +0,0 @@ -import { - updateMyAbout, - updateMySocials, - updateMyClassroomMode -} from '../boot/settings'; - -export const mockReq = opts => { - const req = {}; - return { ...req, ...opts }; -}; - -export const mockRes = opts => { - const res = {}; - res.status = jest.fn().mockReturnValue(res); - res.json = jest.fn().mockReturnValue(res); - res.redirect = jest.fn().mockReturnValue(res); - res.set = jest.fn().mockReturnValue(res); - res.clearCookie = jest.fn().mockReturnValue(res); - res.cookie = jest.fn().mockReturnValue(res); - return { ...res, ...opts }; -}; - -describe('boot/settings', () => { - describe('updateMyAbout', () => { - it('allows empty string in any field', () => { - let updateData; - const req = mockReq({ - user: { - updateAttributes: (update, cb) => { - updateData = update; - cb(); - } - }, - body: { - name: '', - location: '', - about: '', - picture: '' - } - }); - const res = mockRes(); - const next = jest.fn(); - updateMyAbout(req, res, next); - expect(res.status).toHaveBeenCalledWith(200); - expect(updateData).toStrictEqual({ - name: '', - location: '', - about: '', - picture: '' - }); - }); - }); - - describe('updateMySocials', () => { - it('does not allow non-github domain in GitHub social', () => { - const req = mockReq({ - user: {}, - body: { - githubProfile: 'https://www.almost-github.com' - } - }); - const res = mockRes(); - const next = jest.fn(); - updateMySocials(req, res, next); - expect(res.status).toHaveBeenCalledWith(403); - }); - - it('does not allow non-linkedin domain in LinkedIn social', () => { - const req = mockReq({ - user: {}, - body: { - linkedin: 'https://www.freecodecamp.org' - } - }); - const res = mockRes(); - const next = jest.fn(); - updateMySocials(req, res, next); - expect(res.status).toHaveBeenCalledWith(403); - }); - - it('does not allow non-twitter domain in Twitter social', () => { - const req = mockReq({ - user: {}, - body: { - twitter: 'https://www.freecodecamp.org' - } - }); - const res = mockRes(); - const next = jest.fn(); - updateMySocials(req, res, next); - expect(res.status).toHaveBeenCalledWith(403); - }); - - it('allows empty string in any social', () => { - const req = mockReq({ - user: { - updateAttributes: (_, cb) => cb() - }, - body: { - twitter: '', - linkedin: '', - githubProfile: '', - website: '' - } - }); - const res = mockRes(); - const next = jest.fn(); - updateMySocials(req, res, next); - expect(res.status).toHaveBeenCalledWith(200); - }); - - it('allows any valid link in website social', () => { - const req = mockReq({ - user: { - updateAttributes: (_, cb) => cb() - }, - body: { - website: 'https://www.freecodecamp.org' - } - }); - const res = mockRes(); - const next = jest.fn(); - updateMySocials(req, res, next); - expect(res.status).toHaveBeenCalledWith(200); - }); - - it('allows valid links with sub-domains to pass', () => { - const req = mockReq({ - user: { - updateAttributes: (_, cb) => cb() - }, - body: { - githubProfile: 'https://www.gist.github.com', - linkedin: 'https://www.linkedin.com/freecodecamp', - twitter: 'https://www.twitter.com/freecodecamp', - website: 'https://www.example.freecodecamp.org' - } - }); - const res = mockRes(); - const next = jest.fn(); - updateMySocials(req, res, next); - expect(res.status).toHaveBeenCalledWith(200); - }); - }); - - describe('updateMyClassroomMode', () => { - it('does not allow invalid classroomMode', () => { - const req = mockReq({ - user: {}, - body: { - isClassroomAccount: 'invalid' - } - }); - const res = mockRes(); - const next = jest.fn(); - updateMyClassroomMode(req, res, next); - expect(res.status).toHaveBeenCalledWith(403); - }); - - it('allows valid classroomMode', () => { - const req = mockReq({ - user: { - updateAttributes: (_, cb) => cb() - }, - body: { - isClassroomAccount: true - } - }); - const res = mockRes(); - const next = jest.fn(); - updateMyClassroomMode(req, res, next); - expect(res.status).toHaveBeenCalledWith(200); - }); - }); -}); diff --git a/api-server/src/server/component-passport.js b/api-server/src/server/component-passport.js deleted file mode 100644 index 3a8f38de971..00000000000 --- a/api-server/src/server/component-passport.js +++ /dev/null @@ -1,180 +0,0 @@ -import { PassportConfigurator } from '@freecodecamp/loopback-component-passport'; -import dedent from 'dedent'; -import passport from 'passport'; - -import { availableLangs } from '../../../shared/config/i18n'; -import { jwtSecret } from '../../config/secrets'; -import passportProviders from './passport-providers'; -import { setAccessTokenToResponse } from './utils/getSetAccessToken'; -import { - getReturnTo, - getPrefixedLandingPath, - getRedirectParams, - haveSamePath -} from './utils/redirection'; -import { getUserById } from './utils/user-stats'; - -const passportOptions = { - emailOptional: true, - profileToUser: null -}; - -PassportConfigurator.prototype.init = function passportInit(noSession) { - this.app.middleware('session:after', passport.initialize()); - - if (noSession) { - return; - } - - this.app.middleware('session:after', passport.session()); - - // Serialization and deserialization is only required if passport session is - // enabled - - passport.serializeUser((user, done) => done(null, user.id)); - - passport.deserializeUser(async (id, done) => { - const user = await getUserById(id).catch(done); - return done(null, user); - }); -}; - -export function setupPassport(app) { - const configurator = new PassportConfigurator(app); - - configurator.setupModels({ - userModel: app.models.user, - userIdentityModel: app.models.userIdentity, - userCredentialModel: app.models.userCredential - }); - - configurator.init(); - - Object.keys(passportProviders).map(function (strategy) { - let config = passportProviders[strategy]; - config.session = config.session !== false; - - config.customCallback = !config.useCustomCallback - ? null - : createPassportCallbackAuthenticator(strategy, config); - - configurator.configureProvider(strategy, { - ...config, - ...passportOptions - }); - }); -} - -export const devSaveResponseAuthCookies = () => { - return (req, res, next) => { - const user = req.user; - - if (!user) { - return res.redirect('/signin'); - } - - const { accessToken } = user; - - setAccessTokenToResponse({ accessToken }, req, res); - return next(); - }; -}; - -export const devLoginRedirect = () => { - return (req, res) => { - // this mirrors the production approach, but only validates the prefix - let { returnTo, origin, pathPrefix } = getRedirectParams( - req, - ({ returnTo, origin, pathPrefix }) => { - pathPrefix = availableLangs.client.includes(pathPrefix) - ? pathPrefix - : ''; - return { - returnTo, - origin, - pathPrefix - }; - } - ); - - // if returnTo has a trailing slash, we need to remove it before comparing - // it to the prefixed landing path - if (returnTo.slice(-1) === '/') { - returnTo = returnTo.slice(0, -1); - } - const redirectBase = getPrefixedLandingPath(origin, pathPrefix); - returnTo += haveSamePath(redirectBase, returnTo) ? '/learn' : ''; - return res.redirect(returnTo); - }; -}; - -export const createPassportCallbackAuthenticator = - (strategy, config) => (req, res, next) => { - return passport.authenticate( - strategy, - { session: false }, - (err, user, userInfo) => { - if (err) { - return next(err); - } - - const state = req && req.query && req.query.state; - // returnTo, origin and pathPrefix are audited by getReturnTo - let { returnTo, origin, pathPrefix } = getReturnTo(state, jwtSecret); - const redirectBase = getPrefixedLandingPath(origin, pathPrefix); - - const { error, error_description } = req.query; - if (error === 'access_denied') { - const blockedByLaw = - error_description === 'Access denied from your location'; - - // Do not show any error message, instead redirect to the blocked page, with details. - if (blockedByLaw) { - return res.redirectWithFlash(`${redirectBase}/blocked`); - } - - req.flash('info', dedent`${error_description}.`); - return res.redirectWithFlash(`${redirectBase}/learn`); - } - - if (!user || !userInfo) { - return res.redirect('/signin'); - } - - const { accessToken } = userInfo; - const { provider } = config; - if (accessToken && accessToken.id) { - if (provider === 'auth0') { - req.flash('success', 'flash.signin-success'); - } else if (user.email) { - req.flash( - 'info', - dedent` -We are moving away from social authentication for privacy reasons. Next time -we recommend using your email address: ${user.email} to sign in instead. - ` - ); - } - setAccessTokenToResponse({ accessToken }, req, res); - req.login(user); - } - - // TODO: getReturnTo could return a success flag to show a flash message, - // but currently it immediately gets overwritten by a second message. We - // should either change the message if the flag is present or allow - // multiple messages to appear at once. - - if (user.acceptedPrivacyTerms) { - // if returnTo has a trailing slash, we need to remove it before comparing - // it to the prefixed landing path - if (returnTo.slice(-1) === '/') { - returnTo = returnTo.slice(0, -1); - } - returnTo += haveSamePath(redirectBase, returnTo) ? '/learn' : ''; - return res.redirectWithFlash(returnTo); - } else { - return res.redirectWithFlash(`${redirectBase}/email-sign-up`); - } - } - )(req, res, next); - }; diff --git a/api-server/src/server/config.development.js b/api-server/src/server/config.development.js deleted file mode 100644 index 5d197a8a1f1..00000000000 --- a/api-server/src/server/config.development.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - host: '127.0.0.1', - sessionSecret: process.env.SESSION_SECRET, - - github: { - clientID: process.env.GITHUB_ID, - clientSecret: process.env.GITHUB_SECRET - } -}; diff --git a/api-server/src/server/config.json b/api-server/src/server/config.json deleted file mode 100644 index a98fd60bdf3..00000000000 --- a/api-server/src/server/config.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "restApiRoot": "/api", - "host": "127.0.0.1", - "port": 3000, - "legacyExplorer": false, - "remoting": { - "rest": { - "handleErrors": false, - "normalizeHttpPath": false, - "xml": false - }, - "json": { - "strict": false, - "limit": "100kb" - }, - "urlencoded": { - "extended": true, - "limit": "100kb" - }, - "cors": false - } -} diff --git a/api-server/src/server/config.local.js b/api-server/src/server/config.local.js deleted file mode 100644 index 3a5a95a441f..00000000000 --- a/api-server/src/server/config.local.js +++ /dev/null @@ -1,11 +0,0 @@ -var globalConfig = require('../common/config.global'); - -module.exports = { - restApiRoot: globalConfig.restApi, - sessionSecret: process.env.SESSION_SECRET, - - github: { - clientID: process.env.GITHUB_ID, - clientSecret: process.env.GITHUB_SECRET - } -}; diff --git a/api-server/src/server/config.production.js b/api-server/src/server/config.production.js deleted file mode 100644 index 585b30e3b13..00000000000 --- a/api-server/src/server/config.production.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - host: process.env.HOST || 'localhost' -}; diff --git a/api-server/src/server/datasources.development.js b/api-server/src/server/datasources.development.js deleted file mode 100644 index 1c52e784b8a..00000000000 --- a/api-server/src/server/datasources.development.js +++ /dev/null @@ -1,30 +0,0 @@ -const debug = require('debug')('fcc:server:datasources'); -const dsLocal = require('./datasources.production.js'); - -const ds = { - ...dsLocal -}; -// use [MailHog](https://github.com/mailhog/MailHog) if no SES keys are found -if (!process.env.SES_ID) { - ds.mail = { - connector: 'mail', - transport: { - type: 'smtp', - host: process.env.MAILHOG_HOST || 'localhost', - secure: false, - port: 1025, - tls: { - rejectUnauthorized: false - } - }, - auth: { - user: 'test', - pass: 'test' - } - }; - debug(`using MailHog server on port ${ds.mail.transport.port}`); -} else { - debug('using AWS SES to deliver emails'); -} - -module.exports = ds; diff --git a/api-server/src/server/datasources.json b/api-server/src/server/datasources.json deleted file mode 100644 index b3e3ac9cbb1..00000000000 --- a/api-server/src/server/datasources.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "db": { - "name": "db", - "connector": "mongodb", - "allowExtendedOperators": true - }, - "mail": { - "name": "mail", - "connector": "mail" - } -} diff --git a/api-server/src/server/datasources.production.js b/api-server/src/server/datasources.production.js deleted file mode 100644 index 8b49365ab00..00000000000 --- a/api-server/src/server/datasources.production.js +++ /dev/null @@ -1,21 +0,0 @@ -var secrets = require('../../config/secrets'); - -module.exports = { - db: { - connector: 'mongodb', - protocol: 'mongodb+srv', - connectionTimeout: 10000, - url: secrets.db, - useNewUrlParser: true, - useUnifiedTopology: true, - allowExtendedOperators: true - }, - mail: { - connector: 'mail', - transport: { - type: 'ses', - accessKeyId: process.env.SES_ID, - secretAccessKey: process.env.SES_SECRET - } - } -}; diff --git a/api-server/src/server/index.js b/api-server/src/server/index.js deleted file mode 100644 index 73d83b4e815..00000000000 --- a/api-server/src/server/index.js +++ /dev/null @@ -1,116 +0,0 @@ -const path = require('path'); -require('dotenv').config({ path: path.resolve(__dirname, '../../../.env') }); - -const Sentry = require('@sentry/node'); -// const Tracing = require('@sentry/tracing'); -const createDebugger = require('debug'); -const _ = require('lodash'); -const loopback = require('loopback'); -const boot = require('loopback-boot'); -const morgan = require('morgan'); - -const { sentry } = require('../../config/secrets'); -const { setupPassport } = require('./component-passport'); -const { getRedirectParams } = require('./utils/redirection.js'); - -const log = createDebugger('fcc:server'); -const reqLogFormat = ':date[iso] :status :method :response-time ms - :url'; - -const app = loopback(); - -app.set('state namespace', '__fcc__'); -app.set('port', process.env.API_PORT || 3000); -app.set('views', path.join(__dirname, 'views')); -app.use(loopback.token()); -app.use( - morgan(reqLogFormat, { stream: { write: msg => log(_.split(msg, '\n')[0]) } }) -); -app.disable('x-powered-by'); - -const createLogOnce = () => { - let called = false; - return str => { - if (called) { - return null; - } - called = true; - return log(str); - }; -}; -const logOnce = createLogOnce(); - -boot(app, __dirname, err => { - if (err) { - // rethrowing the error here because any error thrown in the boot stage - // is silent - logOnce('The below error was thrown in the boot stage'); - throw err; - } -}); - -setupPassport(app); - -const { db } = app.datasources; -db.on( - 'connected', - _.once(() => log('db connected')) -); - -app.start = _.once(function () { - const server = app.listen(app.get('port'), function () { - app.emit('started'); - log( - 'freeCodeCamp server listening on port %d in %s', - app.get('port'), - app.get('env') - ); - log(`connecting to db at ${db.settings.url}`); - }); - - process.on('SIGINT', () => { - log('Shutting down server'); - server.close(() => { - log('Server is closed'); - }); - log('closing db connection'); - db.disconnect().then(() => { - log('DB connection closed'); - // exit process - // this may close kept alive sockets - process.exit(0); - }); - }); -}); - -if (process.env.FREECODECAMP_NODE_ENV === 'development') { - app.get('/', (req, res) => { - log('Mounting dev root redirect...'); - const { origin } = getRedirectParams(req); - res.redirect(origin); - }); -} - -if (sentry.dsn === 'dsn_from_sentry_dashboard') { - log('Sentry reporting disabled unless DSN is provided.'); -} else { - Sentry.init({ - dsn: sentry.dsn - // integrations: [ - // new Sentry.Integrations.Http({ tracing: true }), - // new Tracing.Integrations.Express({ - // app - // }) - // ], - // // Capture 20% of transactions to avoid - // // overwhelming Sentry and remain within - // // the usage quota - // tracesSampleRate: 0.2 - }); - log('Sentry initialized'); -} - -module.exports = app; - -if (require.main === module) { - app.start(); -} diff --git a/api-server/src/server/manifests/README.md b/api-server/src/server/manifests/README.md deleted file mode 100644 index 7c8d7a802cf..00000000000 --- a/api-server/src/server/manifests/README.md +++ /dev/null @@ -1,8 +0,0 @@ -This folder contains a list of json files representing the name -of revisioned client files. It is empty due to the fact that the -files are generated at runtime and their content is determined by -the content of the files they are derived from. - -Since the build process is not exactly the same on every machine, -these files are not tracked in github otherwise conflicts arise when -building on our servers. diff --git a/api-server/src/server/middleware.json b/api-server/src/server/middleware.json deleted file mode 100644 index df8d4ae9ff8..00000000000 --- a/api-server/src/server/middleware.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "initial:before": { - "./middlewares/sentry-request-handler": {} - }, - "initial": { - "compression": {}, - "cors": { - "params": { - "origin": true, - "credentials": true, - "maxAge": 86400 - } - } - }, - "session": { - "./middlewares/sessions.js": {} - }, - "auth:before": { - "express-flash": {}, - "./middlewares/express-extensions": {}, - "./middlewares/cookie-parser": {}, - "./middlewares/request-authorization": {} - }, - "parse": { - "body-parser#json": {}, - "body-parser#urlencoded": { - "params": { - "extended": true - } - }, - "method-override": {} - }, - "routes:before": { - "helmet#xssFilter": {}, - "helmet#noSniff": {}, - "helmet#frameguard": {}, - "./middlewares/csurf": {}, - "./middlewares/csurf-set-cookie": {}, - "./middlewares/constant-headers": {}, - "./middlewares/csp": {}, - "./middlewares/flash-cheaters": {}, - "./middlewares/passport-login": {}, - "./middlewares/rate-limit": { - "paths": ["/mobile-login"] - } - }, - "files": {}, - "final:after": { - "./middlewares/sentry-error-handler": {}, - "./middlewares/csurf-error-handler": {}, - "./middlewares/error-handlers": {}, - "strong-error-handler": { - "params": { - "debug": false, - "log": true - } - } - } -} diff --git a/api-server/src/server/middlewares/constant-headers.js b/api-server/src/server/middlewares/constant-headers.js deleted file mode 100644 index 0579ec03a4b..00000000000 --- a/api-server/src/server/middlewares/constant-headers.js +++ /dev/null @@ -1,21 +0,0 @@ -import { allowedOrigins } from '../../../config/cors-settings'; - -export default function constantHeaders() { - return function (req, res, next) { - if ( - req.headers && - req.headers.origin && - allowedOrigins.includes(req.headers.origin) - ) { - res.header('Access-Control-Allow-Origin', req.headers.origin); - } else { - res.header('Access-Control-Allow-Origin', process.env.HOME_LOCATION); - } - res.header('Access-Control-Allow-Credentials', true); - res.header( - 'Access-Control-Allow-Headers', - 'Origin, X-Requested-With, Content-Type, Accept' - ); - next(); - }; -} diff --git a/api-server/src/server/middlewares/cookie-parser.js b/api-server/src/server/middlewares/cookie-parser.js deleted file mode 100644 index 4bcb0cde79c..00000000000 --- a/api-server/src/server/middlewares/cookie-parser.js +++ /dev/null @@ -1,4 +0,0 @@ -import cookieParser from 'cookie-parser'; - -const cookieSecret = process.env.COOKIE_SECRET; -export default cookieParser.bind(cookieParser, cookieSecret); diff --git a/api-server/src/server/middlewares/csp.js b/api-server/src/server/middlewares/csp.js deleted file mode 100644 index 4490368b970..00000000000 --- a/api-server/src/server/middlewares/csp.js +++ /dev/null @@ -1,93 +0,0 @@ -import helmet from 'helmet'; - -let trusted = [ - "'self'", - 'https://search.freecodecamp.org', - process.env.HOME_LOCATION, - 'https://' + process.env.AUTH0_DOMAIN -]; - -const host = process.env.HOST || 'localhost'; -const port = process.env.SYNC_PORT || '3000'; - -if (process.env.FREECODECAMP_NODE_ENV !== 'production') { - trusted = trusted.concat([`ws://${host}:${port}`, 'http://localhost:8000']); -} - -export default function csp() { - return helmet.contentSecurityPolicy({ - directives: { - defaultSrc: trusted.concat([ - 'https://*.cloudflare.com', - '*.cloudflare.com' - ]), - connectSrc: trusted.concat([ - 'https://glitch.com', - 'https://*.glitch.com', - 'https://*.glitch.me', - 'https://*.cloudflare.com', - 'https://*.algolia.net' - ]), - scriptSrc: [ - "'unsafe-eval'", - "'unsafe-inline'", - '*.google-analytics.com', - '*.gstatic.com', - 'https://*.cloudflare.com', - '*.cloudflare.com', - 'https://*.gitter.im', - 'https://*.cdnjs.com', - '*.cdnjs.com', - 'https://*.jsdelivr.com', - '*.jsdelivr.com', - '*.twimg.com', - 'https://*.twimg.com', - '*.youtube.com', - '*.ytimg.com' - ].concat(trusted), - styleSrc: [ - "'unsafe-inline'", - '*.gstatic.com', - '*.googleapis.com', - '*.bootstrapcdn.com', - 'https://*.bootstrapcdn.com', - '*.cloudflare.com', - 'https://*.cloudflare.com', - 'https://use.fontawesome.com' - ].concat(trusted), - fontSrc: [ - '*.cloudflare.com', - 'https://*.cloudflare.com', - '*.bootstrapcdn.com', - '*.googleapis.com', - '*.gstatic.com', - 'https://*.bootstrapcdn.com', - 'https://use.fontawesome.com' - ].concat(trusted), - imgSrc: [ - // allow all input since we have user submitted images for - // public profile - '*', - 'data:' - ], - mediaSrc: ['*.bitly.com', '*.amazonaws.com', '*.twitter.com'].concat( - trusted - ), - frameSrc: [ - '*.gitter.im', - '*.gitter.im https:', - '*.youtube.com', - '*.twitter.com', - '*.ghbtns.com', - '*.freecatphotoapp.com', - 'freecodecamp.github.io' - ].concat(trusted) - }, - // set to true if you only want to report errors - reportOnly: false, - // set to true if you want to set all headers - setAllHeaders: false, - // set to true if you want to force buggy CSP in Safari 5 - safari5: false - }); -} diff --git a/api-server/src/server/middlewares/csurf-error-handler.js b/api-server/src/server/middlewares/csurf-error-handler.js deleted file mode 100644 index 2237d5227c4..00000000000 --- a/api-server/src/server/middlewares/csurf-error-handler.js +++ /dev/null @@ -1,12 +0,0 @@ -import { csrfOptions } from './csurf.js'; - -export default function csrfErrorHandler() { - return function (err, req, res, next) { - if (err.code === 'EBADCSRFTOKEN' && req.csrfToken) { - // use the middleware to generate a token. The client sends this back via - // a header - res.cookie('csrf_token', req.csrfToken(), csrfOptions); - } - next(err); - }; -} diff --git a/api-server/src/server/middlewares/csurf-set-cookie.js b/api-server/src/server/middlewares/csurf-set-cookie.js deleted file mode 100644 index 2e79ef82087..00000000000 --- a/api-server/src/server/middlewares/csurf-set-cookie.js +++ /dev/null @@ -1,13 +0,0 @@ -import { csrfOptions } from './csurf.js'; - -export default function setCSRFCookie() { - return function (req, res, next) { - // not all paths require a CSRF token, so the function may not be available. - if (req.csrfToken && !req.cookies.csrf_token) { - // use the middleware to generate a token. The client sends this back via - // a header - res.cookie('csrf_token', req.csrfToken(), csrfOptions); - } - next(); - }; -} diff --git a/api-server/src/server/middlewares/csurf.js b/api-server/src/server/middlewares/csurf.js deleted file mode 100644 index 9dcc9684172..00000000000 --- a/api-server/src/server/middlewares/csurf.js +++ /dev/null @@ -1,26 +0,0 @@ -import csurf from 'csurf'; - -export const csrfOptions = { - domain: process.env.COOKIE_DOMAIN, - sameSite: 'strict', - secure: process.env.FREECODECAMP_NODE_ENV === 'production' -}; - -export default function getCsurf() { - const protection = csurf({ - cookie: { ...csrfOptions, httpOnly: true } - }); - return function csrf(req, res, next) { - const { path } = req; - if ( - /^\/donate\/charge-stripe$|^\/donate\/create-stripe-payment-intent$|^\/coderoad-challenge-completed$/.test( - path - ) - ) { - next(); - } else { - // add the middleware - protection(req, res, next); - } - }; -} diff --git a/api-server/src/server/middlewares/error-handlers.js b/api-server/src/server/middlewares/error-handlers.js deleted file mode 100644 index 1ca01eaf14e..00000000000 --- a/api-server/src/server/middlewares/error-handlers.js +++ /dev/null @@ -1,68 +0,0 @@ -// import { inspect } from 'util'; -// import _ from 'lodash/fp'; -import accepts from 'accepts'; - -import { unwrapHandledError } from '../utils/create-handled-error.js'; -import { getRedirectParams } from '../utils/redirection'; - -const errTemplate = (error, req) => { - const { message, stack } = error; - return ` -Error: ${message} -Is authenticated user: ${!!req.user} -Headers: ${JSON.stringify(req.headers, null, 2)} -Original request: ${req.originalMethod} ${req.originalUrl} -Stack: ${stack} - -// raw -${JSON.stringify(error, null, 2)} - -`; -}; - -const isDev = process.env.FREECODECAMP_NODE_ENV !== 'production'; - -export default function prodErrorHandler() { - // error handling in production. - return function (err, req, res, _next) { - // response for when req.body is bigger than body-parser's size limit - if (err?.type === 'entity.too.large') { - return res.status('413').send('Request payload is too large'); - } - - const { origin } = getRedirectParams(req); - const handled = unwrapHandledError(err); - // respect handled error status - let status = handled.status || err.status || res.statusCode; - if (!handled.status && status < 400) { - status = 500; - } - res.status(status); - - // parse res type - const accept = accepts(req); - // prioritise returning json - const type = accept.type('json', 'html', 'text'); - - const redirectTo = handled.redirectTo || `${origin}/`; - const message = - handled.message || - 'Oops! Something went wrong. Please try again in a moment or contact support@freecodecamp.org if the error persists.'; - - if (isDev) { - console.error(errTemplate(err, req)); - } - - if (type === 'json') { - return res.json({ - type: handled.type || 'danger', - message - }); - } else { - if (typeof req.flash === 'function') { - req.flash(handled.type || 'danger', message); - } - return res.redirectWithFlash(redirectTo); - } - }; -} diff --git a/api-server/src/server/middlewares/express-extensions.js b/api-server/src/server/middlewares/express-extensions.js deleted file mode 100644 index f506665b684..00000000000 --- a/api-server/src/server/middlewares/express-extensions.js +++ /dev/null @@ -1,23 +0,0 @@ -import qs from 'query-string'; - -// add rx methods to express -export default function getExpressExtensions() { - return function expressExtensions(req, res, next) { - res.redirectWithFlash = uri => { - const flash = req.flash(); - res.redirect( - `${uri}?${qs.stringify( - { messages: qs.stringify(flash, { arrayFormat: 'index' }) }, - { arrayFormat: 'index' } - )}` - ); - }; - res.sendFlash = (type, message) => { - if (type && message) { - req.flash(type, message); - } - return res.json(req.flash()); - }; - next(); - }; -} diff --git a/api-server/src/server/middlewares/flash-cheaters.js b/api-server/src/server/middlewares/flash-cheaters.js deleted file mode 100644 index 710092c9a4e..00000000000 --- a/api-server/src/server/middlewares/flash-cheaters.js +++ /dev/null @@ -1,32 +0,0 @@ -import dedent from 'dedent'; - -const ALLOWED_METHODS = ['GET']; -const EXCLUDED_PATHS = [ - '/api/flyers/findOne', - '/challenges/current-challenge', - '/challenges/next-challenge', - '/map-aside', - '/signout' -]; - -export default function flashCheaters() { - return function (req, res, next) { - if ( - ALLOWED_METHODS.indexOf(req.method) !== -1 && - EXCLUDED_PATHS.indexOf(req.path) === -1 && - req.user && - req.url !== '/' && - req.user.isCheater - ) { - req.flash( - 'danger', - dedent` - Upon review, this account has been flagged for academic - dishonesty. If you’re the owner of this account contact - team@freecodecamp.org for details. - ` - ); - } - return next(); - }; -} diff --git a/api-server/src/server/middlewares/ms-username.js b/api-server/src/server/middlewares/ms-username.js deleted file mode 100644 index 710bf187e32..00000000000 --- a/api-server/src/server/middlewares/ms-username.js +++ /dev/null @@ -1,20 +0,0 @@ -import debugFactory from 'debug'; -const log = debugFactory('fcc:boot:user'); - -export function createDeleteMsUsername(app) { - const { MsUsername } = app.models; - - return async function deleteMsUsername(req, res, next) { - try { - await MsUsername.destroyAll({ userId: req.user.id }); - req.msUsernameDeleted = true; - } catch (e) { - req.msUsernameDeleted = false; - log( - `An error occurred deleting Microsoft username for user with id ${req.user.id}` - ); - } - - next(); - }; -} diff --git a/api-server/src/server/middlewares/passport-login.js b/api-server/src/server/middlewares/passport-login.js deleted file mode 100644 index d58b8df13aa..00000000000 --- a/api-server/src/server/middlewares/passport-login.js +++ /dev/null @@ -1,21 +0,0 @@ -import _ from 'lodash'; -import { login } from 'passport/lib/http/request'; -import { Observable } from 'rx'; - -// make login polymorphic -// if supplied callback it works as normal -// if called without callback it returns an observable -// login(user, options?, cb?) => Void|Observable -function login$(...args) { - if (_.isFunction(_.last(args))) { - return login.apply(this, args); - } - return Observable.fromNodeCallback(login).apply(this, args); -} - -export default function passportLogin() { - return (req, res, next) => { - req.login = req.logIn = login$; - next(); - }; -} diff --git a/api-server/src/server/middlewares/rate-limit.js b/api-server/src/server/middlewares/rate-limit.js deleted file mode 100644 index b461039a12c..00000000000 --- a/api-server/src/server/middlewares/rate-limit.js +++ /dev/null @@ -1,23 +0,0 @@ -import rateLimit from 'express-rate-limit'; -import MongoStore from 'rate-limit-mongo'; - -const url = process.env.MONGODB || process.env.MONGOHQ_URL; - -// Rate limit for mobile login -// 10 requests per 15 minute windows -export default function rateLimitMiddleware() { - return rateLimit({ - windowMs: 15 * 60 * 1000, - max: 10, - standardHeaders: true, - legacyHeaders: false, - keyGenerator: req => { - return req.headers['x-forwarded-for'] || 'localhost'; - }, - store: new MongoStore({ - collectionName: 'UserRateLimit', - uri: url, - expireTimeMs: 15 * 60 * 1000 - }) - }); -} diff --git a/api-server/src/server/middlewares/request-authorization.js b/api-server/src/server/middlewares/request-authorization.js deleted file mode 100644 index 11c296199f1..00000000000 --- a/api-server/src/server/middlewares/request-authorization.js +++ /dev/null @@ -1,106 +0,0 @@ -import { isEmpty } from 'lodash'; - -import { jwtSecret as _jwtSecret } from '../../../config/secrets'; - -import { wrapHandledError } from '../utils/create-handled-error'; -import { - getAccessTokenFromRequest, - errorTypes -} from '../utils/getSetAccessToken'; -import { getRedirectParams } from '../utils/redirection'; -import { getUserById as _getUserById } from '../utils/user-stats'; - -const authRE = /^\/auth\//; -const confirmEmailRE = /^\/confirm-email$/; -const newsShortLinksRE = /^\/n\/|^\/p\//; -const publicUserRE = /^\/users\/get-public-profile$/; -const publicUsernameRE = /^\/users\/exists$/; -const resubscribeRE = /^\/resubscribe\//; -const showCertRE = /^\/certificate\/showCert\//; -// note: signin may not have a trailing slash -const signinRE = /^\/signin/; -const statusRE = /^\/status\/ping$/; -const unsubscribedRE = /^\/unsubscribed\//; -const unsubscribeRE = /^\/u\/|^\/unsubscribe\/|^\/ue\//; -// note: this would be replaced by webhooks later -const donateRE = /^\/donate\/charge-stripe$/; -const paymentIntentRE = /^\/donate\/create-stripe-payment-intent$/; -const submitCoderoadChallengeRE = /^\/coderoad-challenge-completed$/; -const mobileLoginRE = /^\/mobile-login\/?$/; - -const _pathsAllowedREs = [ - authRE, - confirmEmailRE, - newsShortLinksRE, - publicUserRE, - publicUsernameRE, - resubscribeRE, - showCertRE, - signinRE, - statusRE, - unsubscribedRE, - unsubscribeRE, - donateRE, - paymentIntentRE, - submitCoderoadChallengeRE, - mobileLoginRE -]; - -export function isAllowedPath(path, pathsAllowedREs = _pathsAllowedREs) { - return pathsAllowedREs.some(re => re.test(path)); -} - -export default function getRequestAuthorisation({ - jwtSecret = _jwtSecret, - getUserById = _getUserById -} = {}) { - return function requestAuthorisation(req, res, next) { - const { origin } = getRedirectParams(req); - const { path } = req; - if (!isAllowedPath(path)) { - const { accessToken, error } = getAccessTokenFromRequest(req, jwtSecret); - if (!accessToken && error === errorTypes.noTokenFound) { - throw wrapHandledError( - new Error('Access token is required for this request'), - { - type: 'info', - redirect: `${origin}/signin`, - message: 'Access token is required for this request', - status: 403 - } - ); - } - if (!accessToken && error === errorTypes.invalidToken) { - throw wrapHandledError(new Error('Access token is invalid'), { - type: 'info', - redirect: `${origin}/signin`, - message: 'Your access token is invalid', - status: 403 - }); - } - if (!accessToken && error === errorTypes.expiredToken) { - throw wrapHandledError(new Error('Access token is no longer valid'), { - type: 'info', - redirect: `${origin}/signin`, - message: 'Access token is no longer valid', - status: 403 - }); - } - if (isEmpty(req.user)) { - const { userId } = accessToken; - return getUserById(userId) - .then(user => { - if (user) { - req.user = user; - } - return; - }) - .then(next) - .catch(next); - } else { - return Promise.resolve(next()); - } - } - return Promise.resolve(next()); - }; -} diff --git a/api-server/src/server/middlewares/request-authorization.test.js b/api-server/src/server/middlewares/request-authorization.test.js deleted file mode 100644 index 86ca46d08a7..00000000000 --- a/api-server/src/server/middlewares/request-authorization.test.js +++ /dev/null @@ -1,180 +0,0 @@ -import path from 'path'; -import jwt from 'jsonwebtoken'; -import { config } from 'dotenv'; - -import { mockReq as mockRequest, mockRes } from '../boot_tests/challenge.test'; -import createRequestAuthorization, { - isAllowedPath -} from './request-authorization'; - -config({ path: path.resolve(__dirname, '../../../../.env') }); - -const validJWTSecret = 'this is a super secret string'; -const invalidJWTSecret = 'This is not correct secret'; -const now = new Date(Date.now()); -const theBeginningOfTime = new Date(0); -const accessToken = { - id: '123abc', - userId: '456def', - ttl: 60000, - created: now -}; -const users = { - '456def': { - username: 'camperbot', - progressTimestamps: [1, 2, 3, 4] - } -}; -const mockGetUserById = id => - id in users ? Promise.resolve(users[id]) : Promise.reject('No user found'); - -const mockReq = args => { - const mock = mockRequest(args); - mock.header = () => process.env.HOME_LOCATION; - return mock; -}; - -describe('request-authorization', () => { - describe('isAllowedPath', () => { - const authRE = /^\/auth\//; - const confirmEmailRE = /^\/confirm-email$/; - const newsShortLinksRE = /^\/n\/|^\/p\//; - const publicUserRE = /^\/users\/get-public-profile$/; - const publicUsernameRE = /^\/users\/exists$/; - const resubscribeRE = /^\/resubscribe\//; - const showCertRE = /^\/certificate\/showCert\//; - // note: signin may not have a trailing slash - const signinRE = /^\/signin/; - const statusRE = /^\/status\/ping$/; - const unsubscribedRE = /^\/unsubscribed\//; - const unsubscribeRE = /^\/u\/|^\/unsubscribe\/|^\/ue\//; - - const allowedPathsList = [ - authRE, - confirmEmailRE, - newsShortLinksRE, - publicUserRE, - publicUsernameRE, - resubscribeRE, - showCertRE, - signinRE, - statusRE, - unsubscribedRE, - unsubscribeRE - ]; - - it('returns a boolean', () => { - const result = isAllowedPath(); - expect(typeof result).toBe('boolean'); - }); - - it('returns true for a white listed path', () => { - const resultA = isAllowedPath( - '/auth/auth0/callback?code=yF_mGjswLsef-_RLo', - allowedPathsList - ); - const resultB = isAllowedPath( - '/ue/WmjInLerysPrcon6fMb/', - allowedPathsList - ); - expect(resultA).toBe(true); - expect(resultB).toBe(true); - }); - - it('returns false for a non-white-listed path', () => { - const resultA = isAllowedPath('/hax0r-42/no-go', allowedPathsList); - const resultB = isAllowedPath( - '/update-current-challenge', - allowedPathsList - ); - expect(resultA).toBe(false); - expect(resultB).toBe(false); - }); - }); - - describe('createRequestAuthorization', () => { - const requestAuthorization = createRequestAuthorization({ - jwtSecret: validJWTSecret, - getUserById: mockGetUserById - }); - - it('is a function', () => { - expect(typeof requestAuthorization).toEqual('function'); - }); - - describe('cookies', () => { - it('throws when no access token is present', () => { - expect.assertions(2); - const req = mockReq({ path: '/some-path/that-needs/auth' }); - const res = mockRes(); - const next = jest.fn(); - expect(() => requestAuthorization(req, res, next)).toThrowError( - 'Access token is required for this request' - ); - expect(next).not.toHaveBeenCalled(); - }); - - it('throws when the access token is invalid', () => { - expect.assertions(2); - const invalidJWT = jwt.sign({ accessToken }, invalidJWTSecret); - const req = mockReq({ - path: '/some-path/that-needs/auth', - // eslint-disable-next-line camelcase - cookie: { jwt_access_token: invalidJWT } - }); - const res = mockRes(); - const next = jest.fn(); - - expect(() => requestAuthorization(req, res, next)).toThrowError( - 'Access token is invalid' - ); - expect(next).not.toHaveBeenCalled(); - }); - - it('throws when the access token has expired', () => { - expect.assertions(2); - const invalidJWT = jwt.sign( - { accessToken: { ...accessToken, created: theBeginningOfTime } }, - validJWTSecret - ); - const req = mockReq({ - path: '/some-path/that-needs/auth', - // eslint-disable-next-line camelcase - cookie: { jwt_access_token: invalidJWT } - }); - const res = mockRes(); - const next = jest.fn(); - - expect(() => requestAuthorization(req, res, next)).toThrowError( - 'Access token is no longer valid' - ); - expect(next).not.toHaveBeenCalled(); - }); - - it('adds the user to the request object', async () => { - expect.assertions(3); - const validJWT = jwt.sign({ accessToken }, validJWTSecret); - const req = mockReq({ - path: '/some-path/that-needs/auth', - // eslint-disable-next-line camelcase - cookie: { jwt_access_token: validJWT } - }); - const res = mockRes(); - const next = jest.fn(); - await requestAuthorization(req, res, next); - expect(next).toHaveBeenCalled(); - expect(req).toHaveProperty('user'); - expect(req.user).toEqual(users['456def']); - }); - - it('calls next if request does not require authorization', async () => { - // currently /unsubscribe does not require authorization - const req = mockReq({ path: '/unsubscribe/another/route' }); - const res = mockRes(); - const next = jest.fn(); - await requestAuthorization(req, res, next); - expect(next).toHaveBeenCalled(); - }); - }); - }); -}); diff --git a/api-server/src/server/middlewares/sentry-error-handler.js b/api-server/src/server/middlewares/sentry-error-handler.js deleted file mode 100644 index c795ff27093..00000000000 --- a/api-server/src/server/middlewares/sentry-error-handler.js +++ /dev/null @@ -1,23 +0,0 @@ -import { Handlers, captureException } from '@sentry/node'; -import { sentry } from '../../../config/secrets'; -import { isHandledError } from '../utils/create-handled-error'; - -// sends directly to Sentry -export function reportError(err) { - return sentry.dsn === 'dsn_from_sentry_dashboard' - ? console.error(err) - : captureException(err); -} - -// determines which errors should be reported -export default function sentryErrorHandler() { - return sentry.dsn === 'dsn_from_sentry_dashboard' - ? (req, res, next) => next() - : Handlers.errorHandler({ - shouldHandleError(err) { - // CSRF errors have status 403, consider ignoring them once csurf is - // no longer rejecting people incorrectly. - return !isHandledError(err) && (!err.status || err.status >= 500); - } - }); -} diff --git a/api-server/src/server/middlewares/sentry-request-handler.js b/api-server/src/server/middlewares/sentry-request-handler.js deleted file mode 100644 index d1b999655eb..00000000000 --- a/api-server/src/server/middlewares/sentry-request-handler.js +++ /dev/null @@ -1,8 +0,0 @@ -import { Handlers } from '@sentry/node'; -import { sentry } from '../../../config/secrets'; - -export default function sentryRequestHandler() { - return sentry.dsn === 'dsn_from_sentry_dashboard' - ? (req, res, next) => next() - : Handlers.requestHandler(); -} diff --git a/api-server/src/server/middlewares/sentry-tracing-handler.js b/api-server/src/server/middlewares/sentry-tracing-handler.js deleted file mode 100644 index bae04bd2f53..00000000000 --- a/api-server/src/server/middlewares/sentry-tracing-handler.js +++ /dev/null @@ -1,8 +0,0 @@ -import { Handlers } from '@sentry/node'; -import { sentry } from '../../../config/secrets'; - -export default function sentryRequestHandler() { - return sentry.dsn === 'dsn_from_sentry_dashboard' - ? (req, res, next) => next() - : Handlers.tracingHandler(); -} diff --git a/api-server/src/server/middlewares/sessions.js b/api-server/src/server/middlewares/sessions.js deleted file mode 100644 index 219ed97724a..00000000000 --- a/api-server/src/server/middlewares/sessions.js +++ /dev/null @@ -1,20 +0,0 @@ -import MongoStoreFactory from 'connect-mongo'; -import session from 'express-session'; - -const MongoStore = MongoStoreFactory(session); -const sessionSecret = process.env.SESSION_SECRET; -const url = process.env.MONGODB || process.env.MONGOHQ_URL; - -export default function sessionsMiddleware() { - return session({ - // 900 day session cookie - cookie: { maxAge: 900 * 24 * 60 * 60 * 1000 }, - // resave forces session to be resaved - // regardless of whether it was modified - // this causes race conditions during parallel req - resave: false, - saveUninitialized: true, - secret: sessionSecret, - store: new MongoStore({ url }) - }); -} diff --git a/api-server/src/server/middlewares/survey.js b/api-server/src/server/middlewares/survey.js deleted file mode 100644 index 082c91f9d03..00000000000 --- a/api-server/src/server/middlewares/survey.js +++ /dev/null @@ -1,42 +0,0 @@ -import debugFactory from 'debug'; -const log = debugFactory('fcc:boot:user'); - -const allowedTitles = ['Foundational C# with Microsoft Survey']; - -export function validateSurvey(req, res, next) { - const { title, responses } = req.body.surveyResults || { - title: '', - responses: [] - }; - - if ( - !allowedTitles.includes(title) || - !Array.isArray(responses) || - !responses.every( - r => typeof r.question === 'string' && typeof r.response === 'string' - ) - ) { - return res.status(400).json({ - type: 'error', - message: 'flash.survey.err-1' - }); - } - - next(); -} - -export function createDeleteUserSurveys(app) { - const { Survey } = app.models; - - return async function deleteUserSurveys(req, res, next) { - try { - await Survey.destroyAll({ userId: req.user.id }); - req.userSurveysDeleted = true; - } catch (e) { - req.userSurveysDeleted = false; - log(`An error occurred deleting Surveys for user with id ${req.user.id}`); - } - - next(); - }; -} diff --git a/api-server/src/server/middlewares/user-token.js b/api-server/src/server/middlewares/user-token.js deleted file mode 100644 index 2ba32871662..00000000000 --- a/api-server/src/server/middlewares/user-token.js +++ /dev/null @@ -1,31 +0,0 @@ -import debugFactory from 'debug'; -const log = debugFactory('fcc:boot:user'); -import jwt from 'jsonwebtoken'; -import { jwtSecret } from '../../../config/secrets'; - -/* - * User tokens for submitting external curriculum are deleted when they sign - * out, reset their account, or delete their account - */ - -export function createDeleteUserToken(app) { - const { UserToken } = app.models; - - return async function deleteUserToken(req, res, next) { - try { - await UserToken.destroyAll({ userId: req.user.id }); - req.userTokenDeleted = true; - } catch (e) { - req.userTokenDeleted = false; - log( - `An error occurred deleting user token for user with id ${req.user.id}` - ); - } - - next(); - }; -} - -export function encodeUserToken(userToken) { - return jwt.sign({ userToken }, jwtSecret); -} diff --git a/api-server/src/server/model-config.json b/api-server/src/server/model-config.json deleted file mode 100644 index 788dcfcba41..00000000000 --- a/api-server/src/server/model-config.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "_meta": { - "sources": [ - "loopback/common/models", - "loopback/server/models", - "../common/models", - "./models" - ] - }, - "AuthToken": { - "dataSource": "db", - "public": false - }, - "AccessToken": { - "dataSource": "db", - "public": false - }, - "ACL": { - "dataSource": "db", - "public": false - }, - "block": { - "dataSource": "db", - "public": true - }, - "challenge": { - "dataSource": "db", - "public": true - }, - "Donation": { - "dataSource": "db", - "public": false - }, - "Email": { - "dataSource": "mail", - "public": false - }, - "Exam": { - "dataSource": "db", - "public": false - }, - "Role": { - "dataSource": "db", - "public": false - }, - "MsUsername": { - "dataSource": "db", - "public": false - }, - "Survey": { - "dataSource": "db", - "public": false - }, - "RoleMapping": { - "dataSource": "db", - "public": false - }, - "userCredential": { - "dataSource": "db", - "public": true - }, - "userIdentity": { - "dataSource": "db", - "public": true - }, - "user": { - "dataSource": "db", - "public": true - }, - "User": { - "dataSource": "db", - "public": false - }, - "UserToken": { - "dataSource": "db", - "public": false - } -} diff --git a/api-server/src/server/models/auth-token.js b/api-server/src/server/models/auth-token.js deleted file mode 100644 index 8feb042662b..00000000000 --- a/api-server/src/server/models/auth-token.js +++ /dev/null @@ -1,15 +0,0 @@ -import { Observable } from 'rx'; - -export default function initializeAuthToken(AuthToken) { - AuthToken.on('dataSourceAttached', () => { - AuthToken.findOne$ = Observable.fromNodeCallback( - AuthToken.findOne.bind(AuthToken) - ); - AuthToken.prototype.validate$ = Observable.fromNodeCallback( - AuthToken.prototype.validate - ); - AuthToken.prototype.destroy$ = Observable.fromNodeCallback( - AuthToken.prototype.destroy - ); - }); -} diff --git a/api-server/src/server/models/auth-token.json b/api-server/src/server/models/auth-token.json deleted file mode 100644 index be263f39320..00000000000 --- a/api-server/src/server/models/auth-token.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "AuthToken", - "base": "AccessToken", - "idInjection": true, - "options": { - "validateUpsert": true - }, - "properties": {}, - "validations": [], - "relations": {}, - "acls": [], - "methods": {} -} diff --git a/api-server/src/server/models/donation.js b/api-server/src/server/models/donation.js deleted file mode 100644 index 6282c9a8772..00000000000 --- a/api-server/src/server/models/donation.js +++ /dev/null @@ -1,71 +0,0 @@ -import debug from 'debug'; -import { Observable } from 'rx'; - -import { reportError } from '../middlewares/sentry-error-handler.js'; -import InMemoryCache from '../utils/in-memory-cache'; - -const log = debug('fcc:boot:donate'); -const fiveMinutes = 1000 * 60 * 5; - -export default function initializeDonation(Donation) { - let activeDonationUpdateInterval = null; - const activeDonationCountCacheTTL = fiveMinutes; - const activeDonationCountCache = InMemoryCache(0, reportError); - const activeDonationsQuery$ = () => - Donation.find$({ - // eslint-disable-next-line no-undefined - where: { endDate: undefined } - }).map(instances => instances.length); - function cleanUp() { - if (activeDonationUpdateInterval) { - clearInterval(activeDonationUpdateInterval); - } - return; - } - - process.on('exit', cleanUp); - - Donation.on('dataSourceAttached', () => { - Donation.find$ = Observable.fromNodeCallback(Donation.find.bind(Donation)); - Donation.findOne$ = Observable.fromNodeCallback( - Donation.findOne.bind(Donation) - ); - - seedTheCache() - .then(setupCacheUpdateInterval) - .catch(err => { - const errMsg = `Error caught seeding the cache: ${err.message}`; - err.message = errMsg; - reportError(err); - }); - }); - - function seedTheCache() { - return new Promise((resolve, reject) => - Observable.defer(activeDonationsQuery$).subscribe(count => { - log('activeDonor count: %d', count); - activeDonationCountCache.update(() => count); - return resolve(); - }, reject) - ); - } - - function setupCacheUpdateInterval() { - activeDonationUpdateInterval = setInterval( - () => - Observable.defer(activeDonationsQuery$).subscribe( - count => { - log('activeDonor count: %d', count); - return activeDonationCountCache.update(() => count); - }, - err => { - const errMsg = `Error caught updating the cache: ${err.message}`; - err.message = errMsg; - reportError(err); - } - ), - activeDonationCountCacheTTL - ); - return null; - } -} diff --git a/api-server/src/server/models/donation.json b/api-server/src/server/models/donation.json deleted file mode 100644 index c1280e8db34..00000000000 --- a/api-server/src/server/models/donation.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "name": "Donation", - "description": "A representation of a donation to freeCodeCamp", - "plural": "donations", - "base": "PersistedModel", - "idInjection": true, - "scopes": {}, - "indexes": {}, - "options": { - "validateUpsert": true - }, - "hidden": [], - "remoting": {}, - "http": {}, - "properties": { - "email": { - "type": "string", - "required": true, - "description": "The email used to create the donation" - }, - "provider": { - "type": "string", - "required": true, - "description": "The payment handler, paypal/stripe etc..." - }, - "amount": { - "type": "number", - "required": true, - "description": "The donation amount in cents" - }, - "duration": { - "type": "string" - }, - "startDate": { - "type": "DateString", - "required": true - }, - "endDate": { - "type": "DateString" - }, - "subscriptionId": { - "type": "string", - "required": true, - "description": "The donation subscription id returned from the provider" - }, - "customerId": { - "type": "string", - "required": true, - "description": "The providers reference for the donor" - } - }, - "validations": [ - { - "amount": { - "type": "number", - "description": "Amount should be >= $1 (100c)", - "min": 100 - }, - "facetName": "server" - } - ], - "relations": { - "user": { - "type": "belongsTo", - "model": "user", - "foreignKey": "userId" - } - }, - "acls": [], - "methods": {} -} diff --git a/api-server/src/server/models/exam.json b/api-server/src/server/models/exam.json deleted file mode 100644 index cb0792e168b..00000000000 --- a/api-server/src/server/models/exam.json +++ /dev/null @@ -1,70 +0,0 @@ -{ - "name": "Exam", - "description": "Exam questions for exam style challenges", - "base": "PersistedModel", - "idInjection": true, - "options": { - "strict": true - }, - "properties": { - "numberOfQuestionsInExam": { - "type": "number", - "required": true - }, - "passingPercent": { - "type": "number", - "required": true - }, - "prerequisites": { - "type": [ - { - "id": "string", - "title": "string" - } - ] - }, - "questions": { - "type": [ - { - "id": "string", - "question": "string", - "wrongAnswers": { - "type": [ - { - "id": "string", - "answer": "string" - } - ], - "required": true - }, - "correctAnswers": { - "type": [ - { - "id": "string", - "answer": "string" - } - ], - "required": true - } - } - ], - "required": true, - "itemType": "Question" - }, - "title": { - "type": "string", - "required": true - } - }, - "validations": [], - "relations": {}, - "acls": [ - { - "accessType": "*", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "DENY" - } - ], - "methods": {} -} diff --git a/api-server/src/server/models/ms-username.json b/api-server/src/server/models/ms-username.json deleted file mode 100644 index 4c3cc583a70..00000000000 --- a/api-server/src/server/models/ms-username.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "MsUsername", - "description": "Microsoft account usernames", - "base": "PersistedModel", - "idInjection": true, - "options": { - "validateUpsert": true - }, - "properties": {}, - "validations": [], - "relations": { - "user": { - "type": "belongsTo", - "model": "user", - "foreignKey": "userId" - } - }, - "acls": [], - "methods": {} -} diff --git a/api-server/src/server/models/survey.json b/api-server/src/server/models/survey.json deleted file mode 100644 index ff3f9833862..00000000000 --- a/api-server/src/server/models/survey.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "Survey", - "description": "Survey responses from campers", - "base": "PersistedModel", - "idInjection": true, - "options": { - "strict": true - }, - "properties": { - "title": { - "type": "string", - "required": true - }, - "responses": { - "type": [ - { - "question": "string", - "response": "string" - } - ], - "required": true - } - }, - "validations": [], - "relations": { - "user": { - "type": "belongsTo", - "model": "user", - "foreignKey": "userId" - } - }, - "acls": [ - { - "accessType": "*", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "DENY" - } - ], - "methods": {} -} diff --git a/api-server/src/server/models/user-token.json b/api-server/src/server/models/user-token.json deleted file mode 100644 index 8fcf782505a..00000000000 --- a/api-server/src/server/models/user-token.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "UserToken", - "description": "Tokens for submitting curricula through CodeRoad", - "base": "AccessToken", - "idInjection": true, - "options": { - "validateUpsert": true - }, - "properties": {}, - "validations": [], - "relations": { - "user": { - "type": "belongsTo", - "model": "user", - "foreignKey": "userId" - } - }, - "acls": [], - "methods": {} -} diff --git a/api-server/src/server/passport-providers.js b/api-server/src/server/passport-providers.js deleted file mode 100644 index dc82d4b2069..00000000000 --- a/api-server/src/server/passport-providers.js +++ /dev/null @@ -1,48 +0,0 @@ -import { auth0 } from '../../config/secrets'; - -const { clientID, clientSecret, domain } = auth0; - -// These don't seem to be used, can they go? -const successRedirect = `${process.env.HOME_LOCATION}/learn`; -const failureRedirect = `${process.env.HOME_LOCATION}/signin`; - -// TODO: can we remove passport-mock-strategy entirely in prod? That would let -// us make passport-mock-strategy a dev dep, as it should be. -const passportProviders = { - devlogin: { - authScheme: 'mock', - provider: 'dev', - module: 'passport-mock-strategy' - }, - local: { - provider: 'local', - module: 'passport-local', - usernameField: 'email', - passwordField: 'password', - authPath: '/auth/local', - successRedirect: successRedirect, - failureRedirect: failureRedirect, - session: true, - failureFlash: true - }, - 'auth0-login': { - provider: 'auth0', - module: 'passport-auth0', - clientID, - clientSecret, - domain, - cookieDomain: process.env.COOKIE_DOMAIN || 'localhost', - callbackURL: `${process.env.API_LOCATION}/auth/auth0/callback`, - authPath: '/auth/auth0', - callbackPath: '/auth/auth0/callback', - useCustomCallback: true, - passReqToCallback: true, - state: false, - successRedirect: successRedirect, - failureRedirect: failureRedirect, - scope: ['openid profile email'], - failureFlash: true - } -}; - -export default passportProviders; diff --git a/api-server/src/server/rss/index.js b/api-server/src/server/rss/index.js deleted file mode 100644 index c6f59b74fea..00000000000 --- a/api-server/src/server/rss/index.js +++ /dev/null @@ -1,78 +0,0 @@ -import compareDesc from 'date-fns/compare_desc'; -import debug from 'debug'; -import _ from 'lodash'; - -import { getLybsynFeed } from './lybsyn'; - -const log = debug('fcc:rss:news-feed'); - -const fiveMinutes = 1000 * 60 * 5; - -class NewsFeed { - constructor() { - this.state = { - readyState: false, - lybsynFeed: [], - combinedFeed: [] - }; - this.refreshFeeds(); - - setInterval(this.refreshFeeds, fiveMinutes); - } - - setState = stateUpdater => { - const newState = stateUpdater(this.state); - this.state = _.merge({}, this.state, newState); - return; - }; - - refreshFeeds = () => { - const currentFeed = this.state.combinedFeed.slice(0); - log('grabbing feeds'); - return Promise.all([getLybsynFeed()]) - .then(([lybsynFeed]) => - this.setState(state => ({ - ...state, - lybsynFeed - })) - ) - .then(() => { - log('crossing the streams'); - const { lybsynFeed } = this.state; - const combinedFeed = [...lybsynFeed].sort((a, b) => { - return compareDesc(a.isoDate, b.isoDate); - }); - this.setState(state => ({ - ...state, - combinedFeed, - readyState: true - })); - }) - .catch(err => { - console.log(err); - this.setState(state => ({ - ...state, - combinedFeed: currentFeed - })); - }); - }; - - getFeed = () => - new Promise(resolve => { - let notReadyCount = 0; - - function waitForReady() { - log('notReadyCount', notReadyCount); - notReadyCount++; - return this.state.readyState || notReadyCount === 5 - ? resolve(this.state.combinedFeed) - : setTimeout(waitForReady, 100); - } - log('are we ready?', this.state.readyState); - return this.state.readyState - ? resolve(this.state.combinedFeed) - : setTimeout(waitForReady, 100); - }); -} - -export default NewsFeed; diff --git a/api-server/src/server/rss/lybsyn.js b/api-server/src/server/rss/lybsyn.js deleted file mode 100644 index a94e92ae57e..00000000000 --- a/api-server/src/server/rss/lybsyn.js +++ /dev/null @@ -1,48 +0,0 @@ -import http from 'http'; -import _ from 'lodash'; - -const lybsynFeed = 'http://freecodecamp.libsyn.com/render-type/json'; - -export function getLybsynFeed() { - return new Promise((resolve, reject) => { - http.get(lybsynFeed, res => { - let raw = ''; - - res.on('data', chunk => { - raw += chunk; - }); - - res.on('error', err => reject(err)); - - res.on('end', () => { - let feed = []; - - try { - feed = JSON.parse(raw); - } catch (err) { - return reject(err); - } - const items = feed - .map(item => - _.pick(item, [ - 'full_item_url', - 'item_title', - 'release_date', - 'item_body_short' - ]) - ) - /* eslint-disable camelcase */ - .map( - ({ full_item_url, item_title, release_date, item_body_short }) => ({ - title: item_title, - extract: item_body_short, - isoDate: new Date(release_date).toISOString(), - link: full_item_url - }) - ); - /* eslint-enable camelcase */ - return resolve(items); - }); - }); - }); -} diff --git a/api-server/src/server/utils/__mocks__/exam.js b/api-server/src/server/utils/__mocks__/exam.js deleted file mode 100644 index 13c524a6a44..00000000000 --- a/api-server/src/server/utils/__mocks__/exam.js +++ /dev/null @@ -1,91 +0,0 @@ -export const examJson = { - id: 1, - numberOfQuestionsInExam: 1, - passingPercent: 70, - questions: [ - { - id: '3bbl2mx2mq', - question: 'Question 1?', - wrongAnswers: [ - { id: 'ex7hii9zup', answer: 'Q1: Wrong Answer 1' }, - { id: 'lmr1ew7m67', answer: 'Q1: Wrong Answer 2' }, - { id: 'qh5sz9qdiq', answer: 'Q1: Wrong Answer 3' }, - { id: 'g489kbwn6a', answer: 'Q1: Wrong Answer 4' }, - { id: '7vu84wl4lc', answer: 'Q1: Wrong Answer 5' }, - { id: 'em59kw6avu', answer: 'Q1: Wrong Answer 6' } - ], - correctAnswers: [ - { id: 'dzlokqdc73', answer: 'Q1: Correct Answer 1' }, - { id: 'f5gk39ske9', answer: 'Q1: Correct Answer 2' } - ] - }, - { - id: 'oqis5gzs0h', - question: 'Question 2?', - wrongAnswers: [ - { id: 'ojhnoxh5r5', answer: 'Q2: Wrong Answer 1' }, - { id: 'onx06if0uh', answer: 'Q2: Wrong Answer 2' }, - { id: 'zbxnsko712', answer: 'Q2: Wrong Answer 3' }, - { id: 'bqv5y68jyp', answer: 'Q2: Wrong Answer 4' }, - { id: 'i5xipitiss', answer: 'Q2: Wrong Answer 5' }, - { id: 'wycrnloajd', answer: 'Q2: Wrong Answer 6' } - ], - correctAnswers: [ - { id: 't9ezcsupdl', answer: 'Q1: Correct Answer 1' }, - { id: 'agert35dk0', answer: 'Q1: Correct Answer 2' } - ] - } - ] -}; - -// failed -export const userExam1 = { - userExamQuestions: [ - { - id: '3bbl2mx2mq', - question: 'Question 1?', - answer: { id: 'dzlokqdc73', answer: 'Q1: Correct Answer 1' } - }, - { - id: 'oqis5gzs0h', - question: 'Question 2?', - answer: { id: 'i5xipitiss', answer: 'Q2: Wrong Answer 5' } - } - ], - examTimeInSeconds: 20 -}; - -// passed -export const userExam2 = { - userExamQuestions: [ - { - id: '3bbl2mx2mq', - question: 'Question 1?', - answer: { id: 'dzlokqdc73', answer: 'Q1: Correct Answer 1' } - }, - { - id: 'oqis5gzs0h', - question: 'Question 2?', - answer: { id: 't9ezcsupdl', answer: 'Q1: Correct Answer 1' } - } - ], - examTimeInSeconds: 20 -}; - -export const mockResults1 = { - numberOfCorrectAnswers: 1, - numberOfQuestionsInExam: 2, - percentCorrect: 50, - passingPercent: 70, - passed: false, - examTimeInSeconds: 20 -}; - -export const mockResults2 = { - numberOfCorrectAnswers: 2, - numberOfQuestionsInExam: 2, - percentCorrect: 100, - passingPercent: 70, - passed: true, - examTimeInSeconds: 20 -}; diff --git a/api-server/src/server/utils/auth.js b/api-server/src/server/utils/auth.js deleted file mode 100644 index dd6d9aad0ee..00000000000 --- a/api-server/src/server/utils/auth.js +++ /dev/null @@ -1,50 +0,0 @@ -const githubRegex = /github/i; -const providerHash = { - facebook: ({ id }) => id, - github: ({ username }) => username, - twitter: ({ username }) => username, - linkedin({ _json }) { - return (_json && _json.publicProfileUrl) || null; - }, - google: ({ id }) => id -}; - -export function getUsernameFromProvider(provider, profile) { - return typeof providerHash[provider] === 'function' - ? providerHash[provider](profile) - : null; -} - -// createProfileAttributes(provider: String, profile: {}) => Object -export function createUserUpdatesFromProfile(provider, profile) { - if (githubRegex.test(provider)) { - return createProfileAttributesFromGithub(profile); - } - return { - [getSocialProvider(provider)]: getUsernameFromProvider( - getSocialProvider(provider), - profile - ) - }; -} -// createProfileAttributes(profile) => profileUpdate -function createProfileAttributesFromGithub(profile) { - const { - profileUrl: githubProfile, - username, - _json: { avatar_url: picture, blog: website, location, bio, name } = {} - } = profile; - return { - name, - username: username.toLowerCase(), - location, - bio, - website, - picture, - githubProfile - }; -} - -export function getSocialProvider(provider) { - return provider.split('-')[0]; -} diff --git a/api-server/src/server/utils/cast-to-observable.js b/api-server/src/server/utils/cast-to-observable.js deleted file mode 100644 index 46e0e6063b6..00000000000 --- a/api-server/src/server/utils/cast-to-observable.js +++ /dev/null @@ -1,11 +0,0 @@ -import { Observable, helpers } from 'rx'; - -export default function castToObservable(maybe) { - if (Observable.isObservable(maybe)) { - return maybe; - } - if (helpers.isPromise(maybe)) { - return Observable.fromPromise(maybe); - } - return Observable.of(maybe); -} diff --git a/api-server/src/server/utils/certTypes.json b/api-server/src/server/utils/certTypes.json deleted file mode 100644 index 38bcd9aa84c..00000000000 --- a/api-server/src/server/utils/certTypes.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "frontEnd": "isFrontEndCert", - "backEnd": "isBackEndCert", - "dataVis": "isDataVisCert", - "respWebDesign": "isRespWebDesignCert", - "frontEndLibs": "isFrontEndLibsCert", - "dataVis2018": "is2018DataVisCert", - "jsAlgoDataStruct": "isJsAlgoDataStructCert", - "apisMicroservices": "isApisMicroservicesCert", - "infosecQa": "isInfosecQaCert", - "qaV7": "isQaCertV7", - "infosecV7": "isInfosecCertV7", - "sciCompPyV7": "isSciCompPyCertV7", - "dataAnalysisPyV7": "isDataAnalysisPyCertV7", - "machineLearningPyV7": "isMachineLearningPyCertV7", - "fullStack": "isFullStackCert", - "relationalDatabaseV8": "isRelationalDatabaseV8", - "collegeAlgebraPyV8": "isCollegeAlgebraPyCertV8", - "foundationalCSharpV8": "isFoundationalCSharpCertV8", - "isJsAlgoDataStructCertV8": "isJsAlgoDataStructCertV8" -} diff --git a/api-server/src/server/utils/cookieConfig.js b/api-server/src/server/utils/cookieConfig.js deleted file mode 100644 index fbb12e7d5fa..00000000000 --- a/api-server/src/server/utils/cookieConfig.js +++ /dev/null @@ -1,6 +0,0 @@ -export function createCookieConfig(req) { - return { - signed: !!req.signedCookies, - domain: process.env.COOKIE_DOMAIN || 'localhost' - }; -} diff --git a/api-server/src/server/utils/create-handled-error.js b/api-server/src/server/utils/create-handled-error.js deleted file mode 100644 index 6b10d2a0ef6..00000000000 --- a/api-server/src/server/utils/create-handled-error.js +++ /dev/null @@ -1,29 +0,0 @@ -const _handledError = Symbol('handledError'); - -export function isHandledError(err) { - return !!err[_handledError]; -} - -export function unwrapHandledError(err) { - return err[_handledError] || {}; -} - -export function wrapHandledError( - err, - { type, message, redirectTo, status = 200 } -) { - err[_handledError] = { type, message, redirectTo, status }; - return err; -} - -// for use with express-validator error formatter -export const createValidatorErrorFormatter = - (type, redirectTo) => - ({ msg }) => - wrapHandledError(new Error(msg), { - type, - message: msg, - redirectTo, - // we default to 400 as these are malformed requests - status: 400 - }); diff --git a/api-server/src/server/utils/date-utils.js b/api-server/src/server/utils/date-utils.js deleted file mode 100644 index f9e11876108..00000000000 --- a/api-server/src/server/utils/date-utils.js +++ /dev/null @@ -1,12 +0,0 @@ -import moment from 'moment-timezone'; - -// day count between two epochs (inclusive) -export function dayCount([head, tail], timezone = 'UTC') { - return Math.ceil( - moment(moment(head).tz(timezone).endOf('day')).diff( - moment(tail).tz(timezone).startOf('day'), - 'days', - true - ) - ); -} diff --git a/api-server/src/server/utils/date-utils.test.js b/api-server/src/server/utils/date-utils.test.js deleted file mode 100644 index 62b83d00b1b..00000000000 --- a/api-server/src/server/utils/date-utils.test.js +++ /dev/null @@ -1,87 +0,0 @@ -import moment from 'moment-timezone'; - -import { dayCount } from './date-utils'; - -const PST = 'America/Los_Angeles'; - -describe('date utils', () => { - describe('dayCount', () => { - it('should return 1 day given epochs of the same day', () => { - expect( - dayCount([ - moment.utc('8/3/2015 3:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf() - ]) - ).toEqual(1); - }); - - it('should return 1 day given same epochs', () => { - expect( - dayCount([ - moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf() - ]) - ).toEqual(1); - }); - - it('should return 2 days when there is a 24 hours difference', () => { - expect( - dayCount([ - moment.utc('8/4/2015 2:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf() - ]) - ).toEqual(2); - }); - - it( - 'should return 2 days when the diff is less than 24h but ' + - 'different in UTC', - () => { - expect( - dayCount([ - moment.utc('8/4/2015 1:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('8/3/2015 23:00', 'M/D/YYYY H:mm').valueOf() - ]) - ).toEqual(2); - } - ); - - it( - 'should return 1 day when the diff is less than 24h ' + - 'and days are different in UTC, but given PST', - () => { - expect( - dayCount( - [ - moment.utc('8/4/2015 1:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('8/3/2015 23:00', 'M/D/YYYY H:mm').valueOf() - ], - PST - ) - ).toEqual(1); - } - ); - - it('should return correct count when there is very big period', () => { - expect( - dayCount([ - moment.utc('10/27/2015 1:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('5/12/1982 1:00', 'M/D/YYYY H:mm').valueOf() - ]) - ).toEqual(12222); - }); - - it( - 'should return 2 days when there is a 24 hours difference ' + - 'between dates given `undefined` timezone', - () => { - expect( - dayCount([ - moment.utc('8/4/2015 2:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf() - ]) - ).toEqual(2); - } - ); - }); -}); diff --git a/api-server/src/server/utils/disabled-endpoints.js b/api-server/src/server/utils/disabled-endpoints.js deleted file mode 100644 index c63f10cb20d..00000000000 --- a/api-server/src/server/utils/disabled-endpoints.js +++ /dev/null @@ -1,17 +0,0 @@ -export function deprecatedEndpoint(_, res) { - return res.status(410).json({ - message: { - type: 'info', - message: 'Please reload the app, this feature is no longer available.' - } - }); -} - -export function temporarilyDisabledEndpoint(_, res) { - return res.status(404).json({ - message: { - type: 'info', - message: 'Please reload the app, this feature is no longer available.' - } - }); -} diff --git a/api-server/src/server/utils/donation.js b/api-server/src/server/utils/donation.js deleted file mode 100644 index a8d9208ffe1..00000000000 --- a/api-server/src/server/utils/donation.js +++ /dev/null @@ -1,181 +0,0 @@ -/* eslint-disable camelcase */ -import debug from 'debug'; -import { donationSubscriptionConfig } from '../../../../shared/config/donation-settings'; - -const log = debug('fcc:boot:donate'); - -export function capitalizeKeys(object) { - Object.keys(object).forEach(function (key) { - object[key.toUpperCase()] = object[key]; - }); -} - -export const createAsyncUserDonation = (user, donation) => { - log(`Creating donation:${donation.subscriptionId}`); - // log user donation - user - .createDonation(donation) - .toPromise() - .catch(err => { - throw new Error(err); - }); -}; - -export async function createStripeCardDonation(req, res, stripe) { - const { - body: { paymentMethodId, amount, duration }, - user: { name, id: userId, email }, - user - } = req; - - if (!paymentMethodId || !amount || !duration || !userId || !email) { - throw { - message: 'Request is not valid', - type: 'InvalidRequest' - }; - } - - /* - * if user is already donating and the donation isn't one time only, - * throw error - */ - - if (user.isDonating && duration !== 'one-time') { - throw { - message: `User already has active recurring donation(s).`, - type: 'AlreadyDonatingError' - }; - } - - /* - * card donations is blocked for new users - */ - - const threeChallengesCompleted = user.completedChallenges.length >= 3; - if (!threeChallengesCompleted) { - throw { - message: `Donate using another method`, - type: 'MethodRestrictionError' - }; - } - - let customerId; - try { - const customer = await stripe.customers.create({ - email, - payment_method: paymentMethodId, - invoice_settings: { default_payment_method: paymentMethodId }, - ...(name && { name }) - }); - customerId = customer?.id; - } catch { - throw { - type: 'customerCreationFailed', - message: 'Failed to create stripe customer' - }; - } - log(`Stripe customer with id ${customerId} created`); - - let subscriptionId; - try { - const { - id: subscription_id, - latest_invoice: { - payment_intent: { client_secret, status: intent_status } - } - } = await stripe.subscriptions.create({ - // create Stripe subscription - customer: customerId, - payment_behavior: 'allow_incomplete', - items: [ - { - plan: `${donationSubscriptionConfig.duration[ - duration - ].toLowerCase()}-donation-${amount}` - } - ], - expand: ['latest_invoice.payment_intent'] - }); - - if (intent_status === 'requires_source_action') - throw { - type: 'UserActionRequired', - message: 'Payment requires user action', - client_secret - }; - else if (intent_status === 'requires_source') - throw { - type: 'PaymentMethodRequired', - message: 'Card has been declined' - }; - subscriptionId = subscription_id; - } catch (err) { - if ( - err.type === 'UserActionRequired' || - err.type === 'PaymentMethodRequired' - ) - throw err; - else - throw { - type: 'SubscriptionCreationFailed', - message: 'Failed to create stripe subscription' - }; - } - log(`Stripe subscription with id ${subscriptionId} created`); - - // save Donation - let donation = { - email, - amount, - duration, - provider: 'stripe', - subscriptionId, - customerId, - startDate: new Date().toISOString() - }; - await createAsyncUserDonation(user, donation); - return res.status(200).json({ isDonating: true }); -} - -export async function handleStripeCardUpdateSession(req, app, stripe) { - const { - user: { id } - } = req; - - const { Donation } = app.models; - log('Updating stripe card for user: ', id); - - // multiple donations support should be added - const donation = await Donation.findOne({ - where: { userId: id, provider: 'stripe' } - }); - - if (!donation) throw Error('Stripe donation record not found'); - - const { customerId, subscriptionId } = donation; - - log(subscriptionId); - - // Create a Stripe checkout session - // updating customer payment method is handled by webhook handler - const session = await stripe.checkout.sessions.create({ - payment_method_types: ['card'], - mode: 'setup', - customer: customerId, - setup_intent_data: { - metadata: { - customer_id: customerId, - subscription_id: subscriptionId - } - }, - success_url: `${process.env.HOME_LOCATION}/update-stripe-card?session_id={CHECKOUT_SESSION_ID}`, - cancel_url: `${process.env.HOME_LOCATION}/update-stripe-card` - }); - return { sessionId: session.id }; -} - -export function inLastFiveMinutes(unixTimestamp) { - const currentTimestamp = Math.floor(Date.now() / 1000); - const timeDifference = currentTimestamp - unixTimestamp; - return timeDifference <= 300; // 300 seconds is 5 minutes -} diff --git a/api-server/src/server/utils/donation.test.js b/api-server/src/server/utils/donation.test.js deleted file mode 100644 index 174299aa166..00000000000 --- a/api-server/src/server/utils/donation.test.js +++ /dev/null @@ -1,59 +0,0 @@ -/* eslint-disable camelcase */ -import stripe from 'stripe'; -import { ObjectId } from 'mongodb'; -import { handleStripeCardUpdateSession } from './donation'; - -jest.mock('stripe', () => ({ - checkout: { - sessions: { - create: jest.fn() - } - } -})); - -describe('donation', () => { - describe('handleStripeCardUpdateSession', () => { - const mockUserId = ObjectId('507f1f77bcf86cd799439011'); - const mockDonation = { - customerId: 'customer_123', - subscriptionId: 'sub_123' - }; - const req = { user: { id: mockUserId } }; - const app = { - models: { - Donation: { findOne: jest.fn().mockResolvedValue(mockDonation) } - } - }; - - stripe.checkout.sessions.create.mockResolvedValue({ id: 'session_123' }); - - it('creates a session successfully', async () => { - const result = await handleStripeCardUpdateSession(req, app, stripe); - expect(app.models.Donation.findOne).toHaveBeenCalledWith({ - where: { userId: mockUserId, provider: 'stripe' } - }); - expect(stripe.checkout.sessions.create).toHaveBeenCalled(); - expect(result).toEqual({ sessionId: 'session_123' }); - }); - - it('throws an error when donation not found', async () => { - const app = { - models: { Donation: { findOne: jest.fn().mockResolvedValue(null) } } - }; - - await expect( - handleStripeCardUpdateSession(req, app, stripe) - ).rejects.toThrow('Stripe donation record not found'); - }); - - it('handles stripe session creation failure', async () => { - stripe.checkout.sessions.create.mockRejectedValue( - new Error('Stripe error') - ); - - await expect( - handleStripeCardUpdateSession(req, app, stripe) - ).rejects.toThrow('Stripe error'); - }); - }); -}); diff --git a/api-server/src/server/utils/exam-schemas.js b/api-server/src/server/utils/exam-schemas.js deleted file mode 100644 index eb67f3d1c99..00000000000 --- a/api-server/src/server/utils/exam-schemas.js +++ /dev/null @@ -1,160 +0,0 @@ -import Joi from 'joi'; -import JoiObjectId from 'joi-objectid'; - -Joi.objectId = JoiObjectId(Joi); - -const nanoIdRE = new RegExp('[a-z0-9]{10}'); - -// Exam from database schema -const DbPrerequisitesJoi = Joi.object().keys({ - id: Joi.objectId().required(), - title: Joi.string() -}); - -const DbAnswerJoi = Joi.object().keys({ - id: Joi.string().regex(nanoIdRE).required(), - deprecated: Joi.bool(), - answer: Joi.string().required() -}); - -const DbQuestionJoi = Joi.object().keys({ - id: Joi.string().regex(nanoIdRE).required(), - question: Joi.string().required(), - deprecated: Joi.bool(), - wrongAnswers: Joi.array() - .items(DbAnswerJoi) - .required() - .custom((value, helpers) => { - const nonDeprecatedCount = value.reduce( - (count, answer) => (answer.deprecated ? count : count + 1), - 0 - ); - const minimumAnswers = 4; - - if (nonDeprecatedCount < minimumAnswers) { - return helpers.message( - `'wrongAnswers' must have at least ${minimumAnswers} non-deprecated answers.` - ); - } - - return value; - }), - correctAnswers: Joi.array() - .items(DbAnswerJoi) - .required() - .custom((value, helpers) => { - const nonDeprecatedCount = value.reduce( - (count, answer) => (answer.deprecated ? count : count + 1), - 0 - ); - const minimumAnswers = 1; - - if (nonDeprecatedCount < minimumAnswers) { - return helpers.message( - `'correctAnswers' must have at least ${minimumAnswers} non-deprecated answer.` - ); - } - - return value; - }) -}); - -const examFromDbSchema = Joi.object().keys({ - // TODO: make sure _id and title match what's in the challenge markdown file - id: Joi.objectId().required(), - title: Joi.string().required(), - numberOfQuestionsInExam: Joi.number() - .min(1) - .max( - Joi.ref('questions', { - adjust: questions => { - const nonDeprecatedCount = questions.reduce( - (count, question) => (question.deprecated ? count : count + 1), - 0 - ); - return nonDeprecatedCount; - } - }) - ) - .required(), - passingPercent: Joi.number().min(0).max(100).required(), - prerequisites: Joi.array().items(DbPrerequisitesJoi), - questions: Joi.array().items(DbQuestionJoi).min(1).required() -}); - -export const validateExamFromDbSchema = exam => { - return examFromDbSchema.validate(exam); -}; - -// Generated Exam Schema -const GeneratedAnswerJoi = Joi.object().keys({ - id: Joi.string().regex(nanoIdRE).required(), - answer: Joi.string().required() -}); - -const GeneratedQuestionJoi = Joi.object().keys({ - id: Joi.string().regex(nanoIdRE).required(), - question: Joi.string().required(), - answers: Joi.array().items(GeneratedAnswerJoi).min(5).required() -}); - -const generatedExamSchema = Joi.array() - .items(GeneratedQuestionJoi) - .min(1) - .required(); - -export const validateGeneratedExamSchema = (exam, numberOfQuestionsInExam) => { - if (!exam.length === numberOfQuestionsInExam) { - throw new Error( - 'The number of exam questions generated does not match the number of questions required.' - ); - } - - return generatedExamSchema.validate(exam); -}; - -// User Completed Exam Schema -const UserCompletedQuestionJoi = Joi.object().keys({ - id: Joi.string().regex(nanoIdRE).required(), - question: Joi.string().required(), - answer: Joi.object().keys({ - id: Joi.string().regex(nanoIdRE).required(), - answer: Joi.string().required() - }) -}); - -const userCompletedExamSchema = Joi.object().keys({ - userExamQuestions: Joi.array() - .items(UserCompletedQuestionJoi) - .min(1) - .required(), - examTimeInSeconds: Joi.number().min(0) -}); - -export const validateUserCompletedExamSchema = ( - exam, - numberOfQuestionsInExam -) => { - // TODO: Validate that the properties exist - if (!exam.length === numberOfQuestionsInExam) { - throw new Error( - 'The number of exam questions answered does not match the number of questions required.' - ); - } - - return userCompletedExamSchema.validate(exam); -}; - -// Exam Results Schema -const examResultsSchema = Joi.object().keys({ - numberOfCorrectAnswers: Joi.number().min(0), - numberOfQuestionsInExam: Joi.number().min(0), - percentCorrect: Joi.number().min(0), - passingPercent: Joi.number().min(0).max(100), - passed: Joi.bool(), - examTimeInSeconds: Joi.number().min(0) -}); - -export const validateExamResultsSchema = examResults => { - return examResultsSchema.validate(examResults); -}; diff --git a/api-server/src/server/utils/exam.js b/api-server/src/server/utils/exam.js deleted file mode 100644 index 61c92323cd4..00000000000 --- a/api-server/src/server/utils/exam.js +++ /dev/null @@ -1,89 +0,0 @@ -import { shuffleArray } from '../../../../shared/utils/shuffle-array'; - -function filterDeprecated(arr) { - return arr.filter(i => !i.deprecated); -} - -function getRandomElement(arr) { - const id = Math.floor(Math.random() * arr.length); - return arr[id]; -} - -// Used to generate a random exam -export function generateRandomExam(examJson) { - const { numberOfQuestionsInExam, questions } = examJson; - const numberOfAnswersPerQuestion = 5; - - const availableQuestions = shuffleArray(filterDeprecated(questions)); - const examQuestions = availableQuestions.slice(0, numberOfQuestionsInExam); - - const randomizedExam = examQuestions.map(question => { - const { correctAnswers, wrongAnswers } = question; - const availableCorrectAnswers = filterDeprecated(correctAnswers); - const availableWrongAnswers = shuffleArray(filterDeprecated(wrongAnswers)); - const correctAnswer = getRandomElement(availableCorrectAnswers); - const answers = shuffleArray([ - correctAnswer, - ...availableWrongAnswers.slice(0, numberOfAnswersPerQuestion - 1) - ]); - return { - id: question.id, - question: question.question, - answers - }; - }); - - return randomizedExam; -} - -// Used to evaluate user completed exams -export function createExamResults(userExam, originalExam) { - const { userExamQuestions, examTimeInSeconds } = userExam; - /** - * Potential Bug: - * numberOfQuestionsInExam and passingPercent come from the exam in the database. - * If either changes between the time a camper starts and submits, it could skew - * the scores. The alternative is to send those to the client and then get them - * back from the client - but then they could be manipulated to cheat. So I think - * this is the way to go. They are unlikely to change, as that would be unfair. We - * could get numberOfQuestionsInExam from userExamQuestions.length - so only the - * passingPercent would come from the database. Maybe that would be better. - */ - const { - questions: originalQuestions, - numberOfQuestionsInExam, - passingPercent - } = originalExam; - - const numberOfCorrectAnswers = userExamQuestions.reduce( - (count, userQuestion) => { - const originalQuestion = originalQuestions.find( - examQuestion => examQuestion.id === userQuestion.id - ); - - if (!originalQuestion) { - throw new Error('An error occurred. Could not find exam question.'); - } - - const isCorrect = originalQuestion.correctAnswers.find( - examAnswer => examAnswer.id === userQuestion.answer.id - ); - return isCorrect ? count + 1 : count; - }, - 0 - ); - - // Percent rounded to one decimal place - const percent = (numberOfCorrectAnswers / numberOfQuestionsInExam) * 100; - const percentCorrect = Math.round(percent * 10) / 10; - const passed = percentCorrect >= passingPercent; - - return { - numberOfCorrectAnswers, - numberOfQuestionsInExam, - percentCorrect, - passingPercent, - passed, - examTimeInSeconds - }; -} diff --git a/api-server/src/server/utils/exam.test.js b/api-server/src/server/utils/exam.test.js deleted file mode 100644 index d810734f620..00000000000 --- a/api-server/src/server/utils/exam.test.js +++ /dev/null @@ -1,54 +0,0 @@ -import { generateRandomExam, createExamResults } from './exam'; - -import { - examJson, - userExam1, - userExam2, - mockResults1, - mockResults2 -} from './__mocks__/exam'; - -describe('Exam helpers', () => { - describe('generateRandomExam()', () => { - const randomizedExam = generateRandomExam(examJson); - - it('should have one question', () => { - expect(randomizedExam.length).toBe(1); - }); - - it('should have five answers', () => { - const firstQuestion = randomizedExam[0]; - expect(firstQuestion.answers.length).toBe(5); - }); - - it('should have exactly one correct answer', () => { - const question = randomizedExam[0]; - const questionId = question.id; - const originalQuestion = examJson.questions.find( - q => q.id === questionId - ); - const originalCorrectAnswer = originalQuestion.correctAnswers; - const correctIds = originalCorrectAnswer.map(a => a.id); - - const numberOfCorrectAnswers = question.answers.filter(a => - correctIds.includes(a.id) - ); - - expect(numberOfCorrectAnswers).toHaveLength(1); - }); - }); - - describe('createExamResults()', () => { - examJson.numberOfQuestionsInExam = 2; - const examResults1 = createExamResults(userExam1, examJson); - const examResults2 = createExamResults(userExam2, examJson); - - it('failing exam should return correct results', () => { - expect(examResults1).toEqual(mockResults1); - }); - - it('passing exam should return correct results', () => { - expect(examResults2).toEqual(mockResults2); - }); - }); -}); diff --git a/api-server/src/server/utils/get-curriculum.js b/api-server/src/server/utils/get-curriculum.js deleted file mode 100644 index 576933898ef..00000000000 --- a/api-server/src/server/utils/get-curriculum.js +++ /dev/null @@ -1,21 +0,0 @@ -import { flatten } from 'lodash'; - -// TODO: keeping curriculum in memory is handy if we want to field requests that -// need to 'query' the curriculum, but if we force the client to handle -// redirectToCurrentChallenge and, instead, only report the current challenge id -// via the user object, then we should *not* store this so it can be garbage -// collected. - -// eslint-disable-next-line import/no-unresolved -import curriculum from '../../../../shared/config/curriculum.json'; - -export function getChallenges() { - return Object.keys(curriculum) - .map(key => curriculum[key].blocks) - .reduce((challengeArray, superBlock) => { - const challengesForBlock = Object.keys(superBlock).map( - key => superBlock[key].challenges - ); - return [...challengeArray, ...flatten(challengesForBlock)]; - }, []); -} diff --git a/api-server/src/server/utils/getSetAccessToken.js b/api-server/src/server/utils/getSetAccessToken.js deleted file mode 100644 index 552122a988d..00000000000 --- a/api-server/src/server/utils/getSetAccessToken.js +++ /dev/null @@ -1,73 +0,0 @@ -import { isBefore } from 'date-fns'; -import jwt from 'jsonwebtoken'; - -import { jwtSecret as _jwtSecret } from '../../../config/secrets'; - -export const jwtCookieNS = 'jwt_access_token'; - -export function createCookieConfig(req) { - return { - signed: !!req.signedCookies, - domain: process.env.COOKIE_DOMAIN - }; -} - -export function setAccessTokenToResponse( - { accessToken }, - req, - res, - jwtSecret = _jwtSecret -) { - const cookieConfig = { - ...createCookieConfig(req), - maxAge: accessToken.ttl || 77760000000 - }; - const jwtAccess = jwt.sign({ accessToken }, jwtSecret); - res.cookie(jwtCookieNS, jwtAccess, cookieConfig); - return; -} - -export function getAccessTokenFromRequest(req, jwtSecret = _jwtSecret) { - const maybeToken = - (req.signedCookies && req.signedCookies[jwtCookieNS]) || - (req.cookie && req.cookie[jwtCookieNS]); - if (!maybeToken) { - return { - accessToken: null, - error: errorTypes.noTokenFound - }; - } - let token; - try { - token = jwt.verify(maybeToken, jwtSecret); - } catch (err) { - return { accessToken: null, error: errorTypes.invalidToken }; - } - - const { accessToken } = token; - const { created, ttl } = accessToken; - const valid = isBefore(Date.now(), Date.parse(created) + ttl); - if (!valid) { - return { - accessToken: null, - error: errorTypes.expiredToken - }; - } - return { accessToken, error: '' }; -} - -export function removeCookies(req, res) { - const config = createCookieConfig(req); - res.clearCookie(jwtCookieNS, config); - res.clearCookie('access_token', config); - res.clearCookie('userId', config); - res.clearCookie('_csrf', config); - res.clearCookie('csrf_token', config); - return; -} - -export const errorTypes = { - noTokenFound: 'No token found', - invalidToken: 'Invalid token', - expiredToken: 'Token timed out' -}; diff --git a/api-server/src/server/utils/getSetAccessToken.test.js b/api-server/src/server/utils/getSetAccessToken.test.js deleted file mode 100644 index f8d2dde7fe6..00000000000 --- a/api-server/src/server/utils/getSetAccessToken.test.js +++ /dev/null @@ -1,126 +0,0 @@ -import jwt from 'jsonwebtoken'; -import { mockReq, mockRes } from '../boot_tests/challenge.test'; -import { - getAccessTokenFromRequest, - errorTypes, - setAccessTokenToResponse, - removeCookies -} from './getSetAccessToken'; - -describe('getSetAccessToken', () => { - const validJWTSecret = 'this is a super secret string'; - const invalidJWTSecret = 'This is not correct secret'; - const now = new Date(Date.now()); - const theBeginningOfTime = new Date(0); - const domain = 'www.example.com'; - const accessToken = { - id: '123abc', - userId: '456def', - ttl: 60000, - created: now - }; - - // https://stackoverflow.com/questions/48033841/test-process-env-with-jest - const OLD_ENV = process.env; - - beforeEach(() => { - jest.resetModules(); // process is implicitly cached by Jest, so hence the reset - process.env = { ...OLD_ENV }; // Shallow clone that we can modify - process.env.COOKIE_DOMAIN = domain; - }); - - afterAll(() => { - process.env = OLD_ENV; - }); - - describe('getAccessTokenFromRequest', () => { - it('return `no token` error if no token is found', () => { - const req = mockReq({ headers: {}, cookie: {} }); - const result = getAccessTokenFromRequest(req, validJWTSecret); - expect(result.error).toEqual(errorTypes.noTokenFound); - }); - - describe('cookies', () => { - it('returns `invalid token` error for malformed tokens', () => { - const invalidJWT = jwt.sign({ accessToken }, invalidJWTSecret); - - const req = mockReq({ cookie: { jwt_access_token: invalidJWT } }); - const result = getAccessTokenFromRequest(req, validJWTSecret); - - expect(result.error).toEqual(errorTypes.invalidToken); - }); - - it('returns `expired token` error for expired tokens', () => { - const invalidJWT = jwt.sign( - { accessToken: { ...accessToken, created: theBeginningOfTime } }, - validJWTSecret - ); - - const req = mockReq({ cookie: { jwt_access_token: invalidJWT } }); - const result = getAccessTokenFromRequest(req, validJWTSecret); - - expect(result.error).toEqual(errorTypes.expiredToken); - }); - - it('returns a valid access token with no errors ', () => { - expect.assertions(2); - const validJWT = jwt.sign({ accessToken }, validJWTSecret); - - const req = mockReq({ cookie: { jwt_access_token: validJWT } }); - const result = getAccessTokenFromRequest(req, validJWTSecret); - - expect(result.error).toBeFalsy(); - expect(result.accessToken).toEqual({ - ...accessToken, - created: accessToken.created.toISOString() - }); - }); - }); - }); - - describe('setAccessTokenToResponse', () => { - it('sets a jwt access token cookie in the response', () => { - const req = mockReq(); - const res = mockRes(); - - const expectedJWT = jwt.sign({ accessToken }, validJWTSecret); - - setAccessTokenToResponse({ accessToken }, req, res, validJWTSecret); - - expect(res.cookie).toHaveBeenNthCalledWith( - 1, - 'jwt_access_token', - expectedJWT, - { - signed: false, - domain, - maxAge: accessToken.ttl - } - ); - }); - }); - - describe('removeCookies', () => { - it('removes four cookies set in the lifetime of an authenticated session', () => { - // expect.assertions(4); - const req = mockReq(); - const res = mockRes(); - const jwtOptions = { signed: false, domain }; - - removeCookies(req, res); - - expect(res.clearCookie).toHaveBeenNthCalledWith( - 1, - 'jwt_access_token', - jwtOptions - ); - expect(res.clearCookie).toHaveBeenNthCalledWith( - 2, - 'access_token', - jwtOptions - ); - expect(res.clearCookie).toHaveBeenNthCalledWith(3, 'userId', jwtOptions); - expect(res.clearCookie).toHaveBeenNthCalledWith(4, '_csrf', jwtOptions); - }); - }); -}); diff --git a/api-server/src/server/utils/in-memory-cache.js b/api-server/src/server/utils/in-memory-cache.js deleted file mode 100644 index d80a33814b5..00000000000 --- a/api-server/src/server/utils/in-memory-cache.js +++ /dev/null @@ -1,43 +0,0 @@ -function isPromiseLike(thing) { - return !!thing && typeof thing.then === 'function'; -} - -function InMemoryCache(initialValue, reportError) { - if (typeof reportError !== 'function') { - throw new Error( - 'No reportError function specified for this in-memory-cache' - ); - } - const cacheKey = Symbol('cacheKey'); - const cache = new Map(); - cache.set(cacheKey, initialValue); - - return { - get() { - const value = cache.get(cacheKey); - return typeof value !== 'undefined' ? value : null; - }, - - update(fn) { - try { - const value = fn(); - if (isPromiseLike(value)) { - return value.then(value => cache.set(cacheKey, value)); - } else { - cache.set(cacheKey, value); - } - } catch (e) { - const errMsg = `InMemoryCache > update > caught: ${e.message}`; - e.message = errMsg; - reportError(e); - } - return null; - }, - - clear() { - return cache.delete(cacheKey); - } - }; -} - -export default InMemoryCache; diff --git a/api-server/src/server/utils/in-memory-cache.test.js b/api-server/src/server/utils/in-memory-cache.test.js deleted file mode 100644 index eeb2544117e..00000000000 --- a/api-server/src/server/utils/in-memory-cache.test.js +++ /dev/null @@ -1,65 +0,0 @@ -import inMemoryCache from './in-memory-cache'; - -describe('InMemoryCache', () => { - let reportErrorStub; - const theAnswer = 42; - const before = 'before'; - const after = 'after'; - const emptyCacheValue = null; - - beforeEach(() => { - reportErrorStub = jest.fn(); - }); - - it('throws if no report function is passed as a second argument', () => { - expect(() => inMemoryCache(null)).toThrowError( - 'No reportError function specified for this in-memory-cache' - ); - }); - - describe('get', () => { - it('returns an initial value', () => { - const cache = inMemoryCache(theAnswer, reportErrorStub); - expect(cache.get()).toBe(theAnswer); - }); - }); - - describe('update', () => { - it('updates the cached value', () => { - const cache = inMemoryCache(before, reportErrorStub); - cache.update(() => after); - expect(cache.get()).toBe(after); - }); - - it('can handle promises correctly', done => { - const cache = inMemoryCache(before, reportErrorStub); - const promisedUpdate = () => new Promise(resolve => resolve(after)); - cache.update(promisedUpdate).then(() => { - expect(cache.get()).toBe(after); - done(); - }); - }); - - it('reports errors thrown from the update function', () => { - const cache = inMemoryCache(before, reportErrorStub); - - const updateError = new Error('An update error'); - const updateThatThrows = () => { - throw updateError; - }; - - cache.update(updateThatThrows); - expect(reportErrorStub).toHaveBeenCalledWith(updateError); - }); - }); - - describe('clear', () => { - it('clears the cache', () => { - expect.assertions(2); - const cache = inMemoryCache(theAnswer, reportErrorStub); - expect(cache.get()).toBe(theAnswer); - cache.clear(); - expect(cache.get()).toBe(emptyCacheValue); - }); - }); -}); diff --git a/api-server/src/server/utils/lang-passthrough-urls.js b/api-server/src/server/utils/lang-passthrough-urls.js deleted file mode 100644 index 3938aa479d3..00000000000 --- a/api-server/src/server/utils/lang-passthrough-urls.js +++ /dev/null @@ -1,4 +0,0 @@ -export default ['auth', 'services', 'link'].reduce((throughs, route) => { - throughs[route] = true; - return throughs; -}, {}); diff --git a/api-server/src/server/utils/middleware.js b/api-server/src/server/utils/middleware.js deleted file mode 100644 index 61144fae64b..00000000000 --- a/api-server/src/server/utils/middleware.js +++ /dev/null @@ -1,108 +0,0 @@ -import dedent from 'dedent'; -import { validationResult } from 'express-validator'; - -import { createValidatorErrorFormatter } from './create-handled-error.js'; - -import { - getAccessTokenFromRequest, - removeCookies -} from './getSetAccessToken.js'; -import { getRedirectParams } from './redirection'; - -export function ifNoUserRedirectHome(message, type = 'errors') { - return function (req, res, next) { - const { path } = req; - if (req.user) { - return next(); - } - - const { origin } = getRedirectParams(req); - req.flash(type, message || `You must be signed in to access ${path}`); - - return res.redirect(origin); - }; -} - -export function ifNoUserSend(sendThis) { - return function (req, res, next) { - if (req.user) { - return next(); - } - return res.status(200).send(sendThis); - }; -} - -export function ifNoUser401(req, res, next) { - if (req.user) { - return next(); - } - return res.status(401).end(); -} - -export function ifNotVerifiedRedirectToUpdateEmail(req, res, next) { - const { user } = req; - if (!user) { - return next(); - } - if (!user.emailVerified) { - req.flash( - 'danger', - dedent` - We do not have your verified email address on record, - please add it in the settings to continue with your request. - ` - ); - return res.redirect('/settings'); - } - return next(); -} - -export function ifUserRedirectTo(status) { - status = status === 301 ? 301 : 302; - return (req, res, next) => { - const { accessToken } = getAccessTokenFromRequest(req); - const { returnTo } = getRedirectParams(req); - if (req.user && accessToken) { - return res.status(status).redirect(returnTo); - } - if (req.user && !accessToken) { - // This request has an active auth session - // but there is no accessToken attached to the request - // perhaps the user cleared cookies? - // we need to remove the zombie auth session - removeCookies(req, res); - delete req.session.passport; - } - return next(); - }; -} - -export function ifNotMobileRedirect() { - return (req, res, next) => { - // - // Todo: Use the below check once we have done more research on usage - // - // const isMobile = /(iPhone|iPad|Android)/.test(req.headers['user-agent']); - // if (!isMobile) { - // res.json({ error: 'not from mobile' }); - // } else { - // next(); - // } - next(); - }; -} -// for use with express-validator error formatter -export const createValidatorErrorHandler = - (...args) => - (req, res, next) => { - const validation = validationResult(req).formatWith( - createValidatorErrorFormatter(...args) - ); - - if (!validation.isEmpty()) { - const errors = validation.array(); - return next(errors.pop()); - } - - return next(); - }; diff --git a/api-server/src/server/utils/publicUserProps.js b/api-server/src/server/utils/publicUserProps.js deleted file mode 100644 index 8fb35d36fea..00000000000 --- a/api-server/src/server/utils/publicUserProps.js +++ /dev/null @@ -1,79 +0,0 @@ -import { isURL } from 'validator'; - -export const publicUserProps = [ - 'about', - 'calendar', - 'completedChallenges', - 'completedExams', - 'completedSurveys', - 'githubProfile', - 'isApisMicroservicesCert', - 'isBackEndCert', - 'isCheater', - 'isDonating', - 'is2018DataVisCert', - 'isDataVisCert', - 'isFrontEndCert', - 'isFullStackCert', - 'isFrontEndLibsCert', - 'isHonest', - 'isInfosecQaCert', - 'isQaCertV7', - 'isInfosecCertV7', - 'isJsAlgoDataStructCert', - 'isRelationalDatabaseCertV8', - 'isRespWebDesignCert', - 'isSciCompPyCertV7', - 'isDataAnalysisPyCertV7', - 'isMachineLearningPyCertV7', - 'isCollegeAlgebraPyCertV8', - 'isFoundationalCSharpCertV8', - 'isJsAlgoDataStructCertV8', - 'linkedin', - 'location', - 'name', - 'partiallyCompletedChallenges', - 'points', - 'portfolio', - 'profileUI', - 'projects', - 'savedChallenges', - 'twitter', - 'username', - 'website', - 'yearsTopContributor' -]; - -export const userPropsForSession = [ - ...publicUserProps, - 'currentChallengeId', - 'email', - 'emailVerified', - 'id', - 'sendQuincyEmail', - 'theme', - 'keyboardShortcuts', - 'completedChallengeCount', - 'acceptedPrivacyTerms' -]; - -export function normaliseUserFields(user) { - const about = user.bio && !user.about ? user.bio : user.about; - const picture = user.picture || ''; - const twitter = - user.twitter && isURL(user.twitter) - ? user.twitter - : user.twitter && - `https://www.twitter.com/${user.twitter.replace(/^@/, '')}`; - return { about, picture, twitter }; -} - -export function getProgress(progressTimestamps) { - const calendar = progressTimestamps - .filter(Boolean) - .reduce((data, timestamp) => { - data[Math.floor(timestamp / 1000)] = 1; - return data; - }, {}); - return { calendar }; -} diff --git a/api-server/src/server/utils/react.js b/api-server/src/server/utils/react.js deleted file mode 100644 index 83492c9ddbe..00000000000 --- a/api-server/src/server/utils/react.js +++ /dev/null @@ -1,6 +0,0 @@ -export const errorThrowerMiddleware = () => next => action => { - if (action.error) { - throw action.payload; - } - return next(action); -}; diff --git a/api-server/src/server/utils/redirection.js b/api-server/src/server/utils/redirection.js deleted file mode 100644 index 93cb37cdfc3..00000000000 --- a/api-server/src/server/utils/redirection.js +++ /dev/null @@ -1,83 +0,0 @@ -const jwt = require('jsonwebtoken'); -const { allowedOrigins } = require('../../../config/cors-settings'); -// process.env.HOME_LOCATION is being used as a fallback here. If the one -// provided by the client is invalid we default to this. -const { availableLangs } = require('../../../../shared/config/i18n'); - -function getReturnTo( - encryptedParams, - secret, - _homeLocation = process.env.HOME_LOCATION -) { - let params; - try { - params = jwt.verify(encryptedParams, secret); - } catch (e) { - // TODO: report to Sentry? Probably not. Remove entirely? - console.log(e); - // something went wrong, use default params - params = { - returnTo: `${_homeLocation}/learn`, - origin: _homeLocation, - pathPrefix: '' - }; - } - - return normalizeParams(params, _homeLocation); -} - -function normalizeParams( - { returnTo, origin, pathPrefix }, - _homeLocation = process.env.HOME_LOCATION -) { - // coerce to strings, just in case something weird and nefarious is happening - returnTo = '' + returnTo; - origin = '' + origin; - pathPrefix = '' + pathPrefix; - // we add the '/' to prevent returns to - // www.freecodecamp.org.somewhere.else.com - if ( - !returnTo || - !allowedOrigins.some(allowed => returnTo.startsWith(allowed + '/')) - ) { - returnTo = `${_homeLocation}/learn`; - origin = _homeLocation; - pathPrefix = ''; - } - if (!origin || !allowedOrigins.includes(origin)) { - returnTo = `${_homeLocation}/learn`; - origin = _homeLocation; - pathPrefix = ''; - } - pathPrefix = availableLangs.client.includes(pathPrefix) ? pathPrefix : ''; - return { returnTo, origin, pathPrefix }; -} - -function getPrefixedLandingPath(origin, pathPrefix) { - const redirectPathSegment = pathPrefix ? `/${pathPrefix}` : ''; - return `${origin}${redirectPathSegment}`; -} - -function getRedirectParams(req, _normalizeParams = normalizeParams) { - const url = req.header('Referer'); - // since we do not always redirect the user back to the page they were on - // we need client locale and origin to construct the redirect url. - const returnUrl = new URL(url ? url : process.env.HOME_LOCATION); - const origin = returnUrl.origin; - // if this is not one of the client languages, validation will convert - // this to '' before it is used. - const pathPrefix = returnUrl.pathname.split('/')[1]; - return _normalizeParams({ returnTo: returnUrl.href, origin, pathPrefix }); -} - -function haveSamePath(redirectBase, returnUrl) { - const base = new URL(redirectBase); - const url = new URL(returnUrl); - return base.pathname === url.pathname; -} - -module.exports.getReturnTo = getReturnTo; -module.exports.getPrefixedLandingPath = getPrefixedLandingPath; -module.exports.normalizeParams = normalizeParams; -module.exports.getRedirectParams = getRedirectParams; -module.exports.haveSamePath = haveSamePath; diff --git a/api-server/src/server/utils/redirection.test.js b/api-server/src/server/utils/redirection.test.js deleted file mode 100644 index ce21db70806..00000000000 --- a/api-server/src/server/utils/redirection.test.js +++ /dev/null @@ -1,121 +0,0 @@ -const jwt = require('jsonwebtoken'); - -const { getReturnTo, normalizeParams } = require('./redirection'); - -const validJWTSecret = 'this is a super secret string'; -const invalidJWTSecret = 'This is not correct secret'; -const validReturnTo = 'https://www.freecodecamp.org/settings'; -const invalidReturnTo = 'https://www.freecodecamp.org.fake/settings'; -const defaultReturnTo = 'https://www.freecodecamp.org/learn'; -const defaultOrigin = 'https://www.freecodecamp.org'; -const defaultPrefix = ''; - -const defaultObject = { - returnTo: defaultReturnTo, - origin: defaultOrigin, - pathPrefix: defaultPrefix -}; - -describe('redirection', () => { - describe('getReturnTo', () => { - it('should extract returnTo from a jwt', () => { - expect.assertions(1); - - const encryptedReturnTo = jwt.sign( - { returnTo: validReturnTo, origin: defaultOrigin }, - validJWTSecret - ); - expect( - getReturnTo(encryptedReturnTo, validJWTSecret, defaultOrigin) - ).toStrictEqual({ - ...defaultObject, - returnTo: validReturnTo - }); - }); - - it('should return a default url if the secrets do not match', () => { - const oldLog = console.log; - expect.assertions(2); - console.log = jest.fn(); - const encryptedReturnTo = jwt.sign( - { returnTo: validReturnTo }, - invalidJWTSecret - ); - expect( - getReturnTo(encryptedReturnTo, validJWTSecret, defaultOrigin) - ).toStrictEqual(defaultObject); - expect(console.log).toHaveBeenCalled(); - console.log = oldLog; - }); - - it('should return a default url for unknown origins', () => { - expect.assertions(1); - const encryptedReturnTo = jwt.sign( - { returnTo: invalidReturnTo }, - validJWTSecret - ); - expect( - getReturnTo(encryptedReturnTo, validJWTSecret, defaultOrigin) - ).toStrictEqual(defaultObject); - }); - }); - describe('normalizeParams', () => { - it('should return a {returnTo, origin, pathPrefix} object', () => { - expect.assertions(2); - const keys = Object.keys(normalizeParams({})); - const expectedKeys = ['returnTo', 'origin', 'pathPrefix']; - expect(keys.length).toBe(3); - expect(keys).toEqual(expect.arrayContaining(expectedKeys)); - }); - it('should default to process.env.HOME_LOCATION', () => { - expect.assertions(1); - expect(normalizeParams({}, defaultOrigin)).toEqual(defaultObject); - }); - it('should convert an unknown pathPrefix to ""', () => { - expect.assertions(1); - const brokenPrefix = { - ...defaultObject, - pathPrefix: 'not-really-a-name' - }; - expect(normalizeParams(brokenPrefix, defaultOrigin)).toEqual( - defaultObject - ); - }); - it('should not change a known pathPrefix', () => { - expect.assertions(1); - const spanishPrefix = { - ...defaultObject, - pathPrefix: 'espanol' - }; - expect(normalizeParams(spanishPrefix, defaultOrigin)).toEqual( - spanishPrefix - ); - }); - // we *could*, in principle, grab the path and send them to - // process.env.HOME_LOCATION/path, but if the origin is wrong something unexpected is - // going on. In that case it's probably best to just send them to - // process.env.HOME_LOCATION/learn. - it('should return default parameters if the origin is unknown', () => { - expect.assertions(1); - const exampleOrigin = { - ...defaultObject, - origin: 'http://example.com', - pathPrefix: 'espanol' - }; - expect(normalizeParams(exampleOrigin, defaultOrigin)).toEqual( - defaultObject - ); - }); - it('should return default parameters if the returnTo is unknown', () => { - expect.assertions(1); - const exampleReturnTo = { - ...defaultObject, - returnTo: 'http://example.com/path', - pathPrefix: 'espanol' - }; - expect(normalizeParams(exampleReturnTo, defaultOrigin)).toEqual( - defaultObject - ); - }); - }); -}); diff --git a/api-server/src/server/utils/rx.js b/api-server/src/server/utils/rx.js deleted file mode 100644 index de1cd7d40bf..00000000000 --- a/api-server/src/server/utils/rx.js +++ /dev/null @@ -1,58 +0,0 @@ -import debugFactory from 'debug'; -import moment from 'moment'; -import Rx, { AsyncSubject, Observable } from 'rx'; - -const debug = debugFactory('fcc:rxUtils'); - -export function saveInstance(instance) { - return new Rx.Observable.create(function (observer) { - if (!instance || typeof instance.save !== 'function') { - debug('no instance or save method'); - observer.onNext(); - return observer.onCompleted(); - } - return instance.save(function (err, savedInstance) { - if (err) { - return observer.onError(err); - } - debug('instance saved'); - observer.onNext(savedInstance); - return observer.onCompleted(); - }); - }); -} - -// alias saveInstance -export const saveUser = saveInstance; - -// observeQuery(Model: Object, methodName: String, query: Any) => Observable -export function observeQuery(Model, methodName, query) { - return Rx.Observable.fromNodeCallback(Model[methodName], Model)(query); -} - -// observeMethod( -// context: Object, methodName: String -// ) => (query: Any) => Observable -export function observeMethod(context, methodName) { - return Rx.Observable.fromNodeCallback(context[methodName], context); -} - -// must be bound to an observable instance -// timeCache(amount: Number, unit: String) => Observable -export function timeCache(time, unit) { - const source = this; - let cache; - let expireCacheAt; - return Observable.create(observable => { - // if there is no expire time set - // or if expireCacheAt is smaller than now, - // set new expire time in MS and create new subscription to source - if (!expireCacheAt || expireCacheAt < Date.now()) { - // set expire in ms; - expireCacheAt = moment().add(time, unit).valueOf(); - cache = new AsyncSubject(); - source.subscribe(cache); - } - return cache.subscribe(observable); - }); -} diff --git a/api-server/src/server/utils/stripeHelpers.js b/api-server/src/server/utils/stripeHelpers.js deleted file mode 100644 index 4c5f30889b1..00000000000 --- a/api-server/src/server/utils/stripeHelpers.js +++ /dev/null @@ -1,10 +0,0 @@ -import { isEmail, isNumeric } from 'validator'; -import { donationSubscriptionConfig } from '../../../../shared/config/donation-settings'; - -export function validStripeForm(amount, duration, email) { - return ( - isEmail('' + email) && - isNumeric('' + amount) && - donationSubscriptionConfig.plans[duration].includes(amount) - ); -} diff --git a/api-server/src/server/utils/url-utils.js b/api-server/src/server/utils/url-utils.js deleted file mode 100644 index 8e2ffb33882..00000000000 --- a/api-server/src/server/utils/url-utils.js +++ /dev/null @@ -1,3 +0,0 @@ -export function getEmailSender() { - return process.env.SES_MAIL_FROM || 'team@freecodecamp.org'; -} diff --git a/api-server/src/server/utils/user-stats.js b/api-server/src/server/utils/user-stats.js deleted file mode 100644 index 3570bfdf2bc..00000000000 --- a/api-server/src/server/utils/user-stats.js +++ /dev/null @@ -1,109 +0,0 @@ -import { isEmpty } from 'lodash'; -import compose from 'lodash/fp/compose'; -import forEachRight from 'lodash/fp/forEachRight'; -import last from 'lodash/fp/last'; -import map from 'lodash/fp/map'; -import sortBy from 'lodash/fp/sortBy'; -import trans from 'lodash/fp/transform'; -import loopback from 'loopback'; -import moment from 'moment-timezone'; - -import { dayCount } from '../utils/date-utils'; - -const transform = trans.convert({ cap: false }); - -const hoursBetween = 24; -const hoursDay = 24; - -export function prepUniqueDaysByHours(cals, tz = 'UTC') { - let prev = null; - - // compose goes bottom to top (map > sortBy > transform) - return compose( - transform((data, cur, i) => { - if (i < 1) { - data.push(cur); - prev = cur; - } else if ( - moment(cur).tz(tz).diff(moment(prev).tz(tz).startOf('day'), 'hours') >= - hoursDay - ) { - data.push(cur); - prev = cur; - } - }, []), - sortBy(e => e), - map(ts => moment(ts).tz(tz).startOf('hours').valueOf()) - )(cals); -} - -export function calcCurrentStreak(cals, tz = 'UTC') { - let prev = last(cals); - if ( - moment().tz(tz).startOf('day').diff(moment(prev).tz(tz), 'hours') > - hoursBetween - ) { - return 0; - } - let currentStreak = 0; - let streakContinues = true; - forEachRight(cur => { - if ( - moment(prev).tz(tz).startOf('day').diff(moment(cur).tz(tz), 'hours') <= - hoursBetween - ) { - prev = cur; - currentStreak++; - } else { - // current streak found - streakContinues = false; - } - return streakContinues; - })(cals); - - return currentStreak; -} - -export function calcLongestStreak(cals, tz = 'UTC') { - let tail = cals[0]; - const longest = cals.reduce( - (longest, head, index) => { - const last = cals[index === 0 ? 0 : index - 1]; - // is streak broken - if ( - moment(head).tz(tz).startOf('day').diff(moment(last).tz(tz), 'hours') > - hoursBetween - ) { - tail = head; - } - if (dayCount(longest, tz) < dayCount([head, tail], tz)) { - return [head, tail]; - } - return longest; - }, - [cals[0], cals[0]] - ); - - return dayCount(longest, tz); -} - -export function getUserById(id, User = loopback.getModelByType('User')) { - return new Promise((resolve, reject) => - User.findById(id, (err, instance) => { - if (err || isEmpty(instance)) { - return reject(err || 'No user instance found'); - } - - let completedChallengeCount = 0; - if ('completedChallenges' in instance) { - completedChallengeCount = instance.completedChallenges.length; - } - - instance.completedChallengeCount = completedChallengeCount; - instance.points = - (instance.progressTimestamps && instance.progressTimestamps.length) || - 1; - return resolve(instance); - }) - ); -} diff --git a/api-server/src/server/utils/user-stats.test.js b/api-server/src/server/utils/user-stats.test.js deleted file mode 100644 index 9f404be2d0f..00000000000 --- a/api-server/src/server/utils/user-stats.test.js +++ /dev/null @@ -1,623 +0,0 @@ -import moment from 'moment-timezone'; - -import { mockUserID, mockApp, mockUser } from '../boot_tests/fixtures'; -import { - prepUniqueDaysByHours, - calcCurrentStreak, - calcLongestStreak, - getUserById -} from './user-stats'; - -jest.useFakeTimers('modern'); -const PST = 'America/Los_Angeles'; - -describe('user stats', () => { - beforeEach(() => { - // setting now to 2016-02-03T11:00:00 (PST) - jest.setSystemTime(1454526000000); - }); - - describe('prepUniqueDaysByHours', () => { - it( - 'should return correct epoch when all entries fall into ' + - 'one day in UTC', - () => { - expect( - prepUniqueDaysByHours([ - moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('8/3/2015 14:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('8/3/2015 20:00', 'M/D/YYYY H:mm').valueOf() - ]) - ).toEqual([1438567200000]); - } - ); - - it('should return correct epoch when given two identical dates', () => { - expect( - prepUniqueDaysByHours([ - moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf() - ]) - ).toEqual([1438567200000]); - }); - - it('should return 2 epochs when dates fall into two days in PST', () => { - expect( - prepUniqueDaysByHours( - [ - // 8/2/2015 in America/Los_Angeles - moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('8/3/2015 14:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('8/3/2015 20:00', 'M/D/YYYY H:mm').valueOf() - ], - PST - ) - ).toEqual([1438567200000, 1438610400000]); - }); - - it('should return 3 epochs when dates fall into three days', () => { - expect( - prepUniqueDaysByHours([ - moment.utc('8/1/2015 2:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('3/3/2015 14:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/30/2014 20:00', 'M/D/YYYY H:mm').valueOf() - ]) - ).toEqual([1412107200000, 1425391200000, 1438394400000]); - }); - - it( - 'should return same but sorted array if all input dates are ' + - 'start of day', - () => { - expect( - prepUniqueDaysByHours([1438387200000, 1425340800000, 1412035200000]) - ).toEqual([1412035200000, 1425340800000, 1438387200000]); - } - ); - }); - - describe('calcCurrentStreak', function () { - it('should return 1 day when today one challenge was completed', () => { - expect( - calcCurrentStreak( - prepUniqueDaysByHours([ - moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ]) - ) - ).toEqual(1); - }); - - it( - 'should return 1 day when today more than one challenge ' + - 'was completed', - () => { - expect( - calcCurrentStreak( - prepUniqueDaysByHours([ - moment.utc(moment.utc().subtract(1, 'hours')).valueOf(), - moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ]) - ) - ).toEqual(1); - } - ); - - it('should return 0 days when today 0 challenges were completed', () => { - expect( - calcCurrentStreak( - prepUniqueDaysByHours([ - moment.utc('9/11/2015 4:00', 'M/D/YYYY H:mm').valueOf() - ]) - ) - ).toEqual(0); - }); - - it( - 'should return 2 days when today and yesterday challenges were ' + - 'completed', - () => { - expect( - calcCurrentStreak( - prepUniqueDaysByHours([ - moment.utc(moment.utc().subtract(1, 'days')).valueOf(), - moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ]) - ) - ).toEqual(2); - } - ); - - it( - 'should return 3 when today and for two days before user was ' + 'active', - () => { - expect( - calcCurrentStreak( - prepUniqueDaysByHours([ - moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/11/2015 4:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/12/2015 1:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/12/2015 1:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/12/2015 2:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/12/2015 3:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/13/2015 4:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/14/2015 5:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc(moment.utc().subtract(2, 'days')).valueOf(), - moment.utc(moment.utc().subtract(1, 'days')).valueOf(), - moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ]) - ) - ).toEqual(3); - } - ); - - it( - 'should return 1 when there is a 1.5 day long break and ' + - 'dates fall into two days separated by third', - () => { - expect( - calcCurrentStreak( - prepUniqueDaysByHours([ - moment.utc(moment.utc().subtract(47, 'hours')).valueOf(), - moment.utc(moment.utc().subtract(11, 'hours')).valueOf() - ]) - ) - ).toEqual(1); - } - ); - - it( - 'should return 2 when the break is more than 1.5 days ' + - 'but dates fall into two consecutive days', - () => { - expect( - calcCurrentStreak( - prepUniqueDaysByHours([ - moment.utc(moment.utc().subtract(40, 'hours')).valueOf(), - moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ]) - ) - ).toEqual(2); - } - ); - - it( - 'should return correct count in default timezone UTC ' + - 'given `undefined` timezone', - () => { - expect( - calcCurrentStreak( - prepUniqueDaysByHours([ - moment.utc(moment.utc().subtract(1, 'hours')).valueOf(), - moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ]) - ) - ).toEqual(1); - } - ); - - it( - 'should return 2 days when today and yesterday ' + - 'challenges were completed given PST', - () => { - expect( - calcCurrentStreak( - prepUniqueDaysByHours( - [ - moment.utc(moment.utc().subtract(1, 'days')).valueOf(), - moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ], - PST - ), - PST - ) - ).toEqual(2); - } - ); - - it( - 'should return 17 when there is no break in given timezone ' + - '(but would be the break if in UTC)', - () => { - expect( - calcCurrentStreak( - prepUniqueDaysByHours( - [ - 1453174506164, - 1453175436725, - 1453252466853, - 1453294968225, - 1453383782844, - 1453431903117, - 1453471373080, - 1453594733026, - 1453645014058, - 1453746762747, - 1453747659197, - 1453748029416, - 1453818029213, - 1453951796007, - 1453988570615, - 1454069704441, - 1454203673979, - 1454294055498, - 1454333545125, - 1454415163903, - 1454519128123, - moment.tz(PST).valueOf() - ], - PST - ), - PST - ) - ).toEqual(17); - } - ); - - it( - 'should return 4 when there is a break in UTC ' + - '(but would be no break in PST)', - () => { - expect( - calcCurrentStreak( - prepUniqueDaysByHours([ - 1453174506164, - 1453175436725, - 1453252466853, - 1453294968225, - 1453383782844, - 1453431903117, - 1453471373080, - 1453594733026, - 1453645014058, - 1453746762747, - 1453747659197, - 1453748029416, - 1453818029213, - 1453951796007, - 1453988570615, - 1454069704441, - 1454203673979, - 1454294055498, - 1454333545125, - 1454415163903, - 1454519128123, - moment.utc().valueOf() - ]) - ) - ).toEqual(4); - } - ); - }); - - describe('calcLongestStreak', function () { - it( - 'should return 1 when there is the only one one-day-long ' + - 'streak available', - () => { - expect( - calcLongestStreak( - prepUniqueDaysByHours([ - moment.utc('9/12/2015 4:00', 'M/D/YYYY H:mm').valueOf() - ]) - ) - ).toEqual(1); - } - ); - - it( - 'should return 4 when there is the only one ' + - 'more-than-one-days-long streak available', - () => { - expect( - calcLongestStreak( - prepUniqueDaysByHours([ - moment.utc('9/11/2015 4:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/12/2015 2:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/13/2015 3:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/14/2015 1:00', 'M/D/YYYY H:mm').valueOf() - ]) - ) - ).toEqual(4); - } - ); - - it( - 'should return 1 when there is only one one-day-long streak ' + - 'and it is today', - () => { - expect( - calcLongestStreak( - prepUniqueDaysByHours([ - moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ]) - ) - ).toEqual(1); - } - ); - - it('should return 2 when yesterday and today makes longest streak', () => { - expect( - calcLongestStreak( - prepUniqueDaysByHours([ - moment.utc(moment.utc().subtract(1, 'days')).valueOf(), - moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ]) - ) - ).toEqual(2); - }); - - it('should return 4 when there is a month long break', () => { - expect( - calcLongestStreak( - prepUniqueDaysByHours([ - moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/11/2015 4:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/12/2015 2:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('10/4/2015 1:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('10/5/2015 5:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('10/6/2015 4:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('10/7/2015 5:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('11/3/2015 2:00', 'M/D/YYYY H:mm').valueOf() - ]) - ) - ).toEqual(4); - }); - - it( - 'should return 2 when there is a more than 1.5 days ' + - 'long break of (36 hours)', - () => { - expect( - calcLongestStreak( - prepUniqueDaysByHours([ - moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/11/2015 4:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/12/2015 15:30', 'M/D/YYYY H:mm').valueOf(), - moment - .utc( - moment - .utc('9/12/2015 15:30', 'M/D/YYYY H:mm') - .add(37, 'hours') - ) - .valueOf(), - moment.utc('9/14/2015 22:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/15/2015 4:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('10/3/2015 2:00', 'M/D/YYYY H:mm').valueOf() - ]) - ) - ).toEqual(2); - } - ); - - it( - 'should return 4 when the longest streak consist of ' + - 'several same day timestamps', - () => { - expect( - calcLongestStreak( - prepUniqueDaysByHours([ - moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/11/2015 4:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/12/2015 1:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/12/2015 1:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/12/2015 2:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/12/2015 3:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/13/2015 4:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/14/2015 5:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc(moment.utc().subtract(2, 'days')).valueOf(), - moment.utc(moment.utc().subtract(1, 'days')).valueOf(), - moment.utc().valueOf() - ]) - ) - ).toEqual(4); - } - ); - - it( - 'should return 4 when there are several longest streaks ' + - '(same length)', - () => { - expect( - calcLongestStreak( - prepUniqueDaysByHours([ - moment.utc('8/3/2015 2:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/11/2015 4:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/12/2015 1:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/13/2015 4:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/14/2015 5:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('10/11/2015 4:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('10/12/2015 1:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('10/13/2015 4:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('10/14/2015 5:00', 'M/D/YYYY H:mm').valueOf() - ]) - ) - ).toEqual(4); - } - ); - - it( - 'should return correct longest streak when there is a very ' + - 'long period', - () => { - let cals = []; - const n = 100; - for (let i = 0; i < n; i++) { - cals.push(moment.utc(moment.utc().subtract(i, 'days')).valueOf()); - } - - expect(calcLongestStreak(prepUniqueDaysByHours(cals))).toEqual(n); - } - ); - - it( - 'should return correct longest streak in default timezone ' + - 'UTC given `undefined` timezone', - () => { - expect( - calcLongestStreak( - prepUniqueDaysByHours([ - moment.utc(moment.utc().subtract(1, 'days')).valueOf(), - moment.utc(moment.utc().subtract(1, 'hours')).valueOf() - ]) - ) - ).toEqual(2); - } - ); - - it( - 'should return 4 when there is the only one more-than-one-days-long ' + - 'streak available given PST', - () => { - expect( - calcLongestStreak( - prepUniqueDaysByHours([ - moment.utc('9/11/2015 4:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/12/2015 2:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/13/2015 3:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/14/2015 1:00', 'M/D/YYYY H:mm').valueOf() - ]), - PST - ) - ).toEqual(4); - } - ); - - it( - 'should return 3 when longest streak is 3 in PST ' + - '(but would be different in default timezone UTC)', - () => { - expect( - calcLongestStreak( - prepUniqueDaysByHours( - [ - moment.utc('9/11/2015 23:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/12/2015 2:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/13/2015 2:00', 'M/D/YYYY H:mm').valueOf(), - moment.utc('9/14/2015 6:00', 'M/D/YYYY H:mm').valueOf() - ], - PST - ), - PST - ) - ).toEqual(3); - } - ); - - it( - 'should return 17 when there is no break in PST ' + - '(but would be break in UTC) and it is current', - () => { - expect( - calcLongestStreak( - prepUniqueDaysByHours( - [ - 1453174506164, - 1453175436725, - 1453252466853, - 1453294968225, - 1453383782844, - 1453431903117, - 1453471373080, - 1453594733026, - 1453645014058, - 1453746762747, - 1453747659197, - 1453748029416, - 1453818029213, - 1453951796007, - 1453988570615, - 1454069704441, - 1454203673979, - 1454294055498, - 1454333545125, - 1454415163903, - 1454519128123, - moment.tz(PST).valueOf() - ], - PST - ), - PST - ) - ).toEqual(17); - } - ); - - it( - 'should return a streak of 4 when there is a break in UTC ' + - '(but no break in PST)', - () => { - expect( - calcLongestStreak( - prepUniqueDaysByHours([ - 1453174506164, - 1453175436725, - 1453252466853, - 1453294968225, - 1453383782844, - 1453431903117, - 1453471373080, - 1453594733026, - 1453645014058, - 1453746762747, - 1453747659197, - 1453748029416, - 1453818029213, - 1453951796007, - 1453988570615, - 1454069704441, - 1454203673979, - 1454294055498, - 1454333545125, - 1454415163903, - 1454519128123, - moment.utc().valueOf() - ]) - ) - ).toEqual(4); - } - ); - }); - - describe('getUserById', () => { - const stubUser = { - findById(id, cb) { - cb(null, { id: 123 }); - } - }; - it('returns a promise', () => { - expect.assertions(3); - expect(typeof getUserById('123', stubUser).then).toEqual('function'); - expect(typeof getUserById('123', stubUser).catch).toEqual('function'); - expect(typeof getUserById('123', stubUser).finally).toEqual('function'); - }); - - it('resolves a user for a given id', done => { - expect.assertions(4); - getUserById(mockUserID, mockApp.models.User) - .then(user => { - expect(user).toEqual(mockUser); - - expect(user).toHaveProperty('progressTimestamps'); - expect(user).toHaveProperty('completedChallengeCount'); - expect(user).toHaveProperty('completedChallenges'); - }) - .then(done) - .catch(done); - }); - - it('throws when no user is found', done => { - const noUserError = new Error('No user found'); - const throwyUserModel = { - findById(_, cb) { - cb(noUserError); - } - }; - expect( - getUserById('not-a-real-id', throwyUserModel).catch(error => { - expect(error).toEqual(noUserError); - done(); - }) - ); - }); - }); -}); diff --git a/api-server/src/server/utils/validators.js b/api-server/src/server/utils/validators.js deleted file mode 100644 index 7532e8d5ac1..00000000000 --- a/api-server/src/server/utils/validators.js +++ /dev/null @@ -1,30 +0,0 @@ -// Refer : http://stackoverflow.com/a/430240/1932901 -function trimTags(value) { - const tagBody = '(?:[^"\'>]|"[^"]*"|\'[^\']*\')*'; - const tagOrComment = new RegExp( - '<(?:' + - // Comment body. - '!--(?:(?:-*[^->])*--+|-?)' + - // Special "raw text" elements whose content should be elided. - '|script\\b' + - tagBody + - '>[\\s\\S]*?[\\s\\S]*?', - 'gi' - ); - let rawValue; - do { - rawValue = value; - value = value.replace(tagOrComment, ''); - } while (value !== rawValue); - - return value.replace(/ has completed all six certifications! Completed -Responsive Web Design Certification on <%= responsiveWebDesignDate %>. Completed -Front End Development Libraries Certification on <%= frontEndLibrariesDate %>. -Completed JavaScript Algorithms and Data Structures Certification on <%= -javascriptAlgorithmsDataStructuresDate %>. Completed Data Visualization -Certification on <%= dataVisualizationDate %>. Completed API's and microservices -Certification on <%= apisMicroservicesDate %>. Completed Information Security -and Quality Assurance Certification on <%= infosecQADate %>. -https://www.freecodecamp.org/<%= username %> diff --git a/api-server/src/server/views/emails/certified.ejs b/api-server/src/server/views/emails/certified.ejs deleted file mode 100644 index fad385ea3d4..00000000000 --- a/api-server/src/server/views/emails/certified.ejs +++ /dev/null @@ -1,15 +0,0 @@ -Hi <%= name || username %>, - -Congratulations on completing all of the freeCodeCamp certifications! - -All of your certifications are now live at at: https://www.freecodecamp.org/<%= username %> - -Please tell me a bit more about you and your near-term goals. - -Are you interested in contributing to our open source projects used by nonprofits? - -Also, check out https://contribute.freecodecamp.org/ for some fun and convenient ways you can contribute to the community. - -Happy coding, - -- Quincy Larson, teacher at freeCodeCamp diff --git a/api-server/src/server/views/emails/user-request-sign-in.ejs b/api-server/src/server/views/emails/user-request-sign-in.ejs deleted file mode 100644 index 71c9f87870c..00000000000 --- a/api-server/src/server/views/emails/user-request-sign-in.ejs +++ /dev/null @@ -1,9 +0,0 @@ -Here's your sign in link. It will instantly sign you into freeCodeCamp.org - no password necessary: - -<%= host %>/passwordless-auth/?email=<%= loginEmail %>&token=<%= loginToken %> - -Note: this sign in link will expire after 15 minutes. If you need a new sign in link, go to https://www.freecodecamp.org/signin - -See you soon! - -- The freeCodeCamp.org Team diff --git a/api-server/src/server/views/emails/user-request-sign-up.ejs b/api-server/src/server/views/emails/user-request-sign-up.ejs deleted file mode 100644 index 42f3b140edb..00000000000 --- a/api-server/src/server/views/emails/user-request-sign-up.ejs +++ /dev/null @@ -1,13 +0,0 @@ -Welcome to the freeCodeCamp community! - -We have created a new account for you. - -Here's your sign in link. It will instantly sign you into freeCodeCamp.org - no password necessary: - -<%= host %>/passwordless-auth/?email=<%= loginEmail %>&token=<%= loginToken %> - -Note: this sign in link will expire after 15 minutes. If you need a new sign in link, go to https://www.freecodecamp.org/signin - -See you soon! - -- The freeCodeCamp.org Team diff --git a/api-server/src/server/views/emails/user-request-update-email.ejs b/api-server/src/server/views/emails/user-request-update-email.ejs deleted file mode 100644 index 38323716901..00000000000 --- a/api-server/src/server/views/emails/user-request-update-email.ejs +++ /dev/null @@ -1,7 +0,0 @@ -Please confirm this email address for freeCodeCamp.org: - -<%= host %>/confirm-email?email=<%= loginEmail %>&token=<%= loginToken %>&emailChange=<%= emailChange %> - -Happy coding! - -- The freeCodeCamp.org Team diff --git a/api/package.json b/api/package.json index 998dd8034ca..7a3893ea64b 100644 --- a/api/package.json +++ b/api/package.json @@ -69,7 +69,6 @@ "scripts": { "build": "tsc -p tsconfig.build.json", "clean": "rm -rf dist", - "dev": "pnpm develop", "develop": "tsx watch --clear-screen=false src/server.ts", "start": "FREECODECAMP_NODE_ENV=production node dist/server.js", "test": "jest --force-exit", diff --git a/babel.config.js b/babel.config.js index 57e78d45062..682ff094010 100644 --- a/babel.config.js +++ b/babel.config.js @@ -1,3 +1,3 @@ module.exports = { - babelrcRoots: ['./api-server', './client'] + babelrcRoots: ['./client'] }; diff --git a/eslint.config.mjs b/eslint.config.mjs index 394b86d93f9..7d46d914621 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -32,7 +32,6 @@ export default tseslint.config( 'client/static/**/*', 'client/.cache/**/*', 'client/public/**/*', - 'api-server/**/*', 'shared/**/*.js', 'docs/**/*.md', '**/playwright*.config.ts', diff --git a/jest.config.js b/jest.config.js index 5d914b263a3..7cef17b8799 100644 --- a/jest.config.js +++ b/jest.config.js @@ -18,7 +18,7 @@ module.exports = { transform: { '^.+\\.[jt]sx?$': '/jest.transform.js' }, - roots: ['.', './client', './api-server'], + roots: ['.', './client'], transformIgnorePatterns: ['node_modules/.pnpm/(?!(nanoid|uuid)@)'], setupFilesAfterEnv: ['./jest.setup.js'], testEnvironment: 'jsdom', diff --git a/knip.jsonc b/knip.jsonc index ca67c6530fe..0ae08f87490 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -1,7 +1,6 @@ { "$schema": "https://cdn.jsdelivr.net/npm/knip@5/schema.json", "ignoreBinaries": ["create:shared", "install-puppeteer", "pm2"], - "ignoreWorkspaces": ["api-server"], // Ignored based on https://github.com/freeCodeCamp/freeCodeCamp/pull/52330#issuecomment-1807917235 "workspaces": { ".": { "playwright": ["playwright.config.ts"], diff --git a/package.json b/package.json index 3e6e5c55ee7..adc46dc788b 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "build-workers": "cd ./client && pnpm run prebuild", "build:client": "cd ./client && pnpm run build", "build:curriculum": "cd ./curriculum && pnpm run build", - "build:server": "cd ./api-server && pnpm run build", + "build:api": "cd ./api && pnpm run build", "challenge-editor": "npm-run-all -p challenge-editor:*", "challenge-editor:client": "cd ./tools/challenge-editor/client && pnpm start", "challenge-editor:server": "cd ./tools/challenge-editor/api && pnpm start", @@ -36,14 +36,13 @@ "clean:client": "cd ./client && pnpm run clean", "clean:curriculum": "rm -rf ./shared/config/curriculum.json", "clean:packages": "find . -name 'node_modules' -type d -prune -exec rm -rf '{}' +", - "clean:server": "rm -rf ./api-server/lib", "create:shared": "tsc -p shared", "create-new-project": "cd ./tools/challenge-helper-scripts/ && pnpm run create-project", "create-new-quiz": "cd ./tools/challenge-helper-scripts/ && pnpm run create-quiz", "predevelop": "npm-run-all -p create:shared -s build:curriculum", "develop": "npm-run-all -p develop:*", "develop:client": "cd ./client && pnpm run develop", - "develop:server": "cd ./api-server && pnpm run develop", + "develop:api": "cd ./api && pnpm run develop", "format": "run-s format:eslint format:prettier", "format:eslint": "eslint . --fix", "format:prettier": "prettier --write .", @@ -69,8 +68,6 @@ "serve:client": "cd ./client && pnpm run serve", "serve:client-ci": "cd ./client && pnpm run serve-ci", "start": "npm-run-all create:shared -p develop:server serve:client", - "start-ci": "npm-run-all create:shared -p start:server serve:client-ci", - "start:server": "pm2 start api-server/ecosystem.config.js", "test": "NODE_OPTIONS='--max-old-space-size=7168' run-s create:shared build:curriculum build-workers test:*", "test:source": "jest", "test:api": "cd api && jest --force-exit", @@ -79,7 +76,6 @@ "test-client": "jest client", "test-config": "jest config", "test-curriculum-js": "jest curriculum", - "test-server": "jest api-server", "test-tools": "jest tools", "test-utils": "jest utils", "prepare": "husky", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 541b634b201..e621a49c88f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -295,175 +295,6 @@ importers: specifier: 4.19.1 version: 4.19.1 - api-server: - dependencies: - '@freecodecamp/loopback-component-passport': - specifier: 1.2.0 - version: 1.2.0 - '@sentry/node': - specifier: 7.37.1 - version: 7.37.1 - '@sentry/tracing': - specifier: 7.37.1 - version: 7.37.1 - accepts: - specifier: 1.3.8 - version: 1.3.8 - body-parser: - specifier: 1.20.0 - version: 1.20.0 - compression: - specifier: 1.7.4 - version: 1.7.4 - connect-mongo: - specifier: 3.2.0 - version: 3.2.0(express-session@1.17.3) - cookie-parser: - specifier: 1.4.6 - version: 1.4.6 - cors: - specifier: 2.8.5 - version: 2.8.5 - csurf: - specifier: 1.11.0 - version: 1.11.0 - date-fns: - specifier: 1.30.1 - version: 1.30.1 - debug: - specifier: 2.2.0 - version: 2.2.0 - dedent: - specifier: 0.7.0 - version: 0.7.0 - dotenv: - specifier: 6.2.0 - version: 6.2.0 - express-flash: - specifier: 0.0.2 - version: 0.0.2 - express-rate-limit: - specifier: ^6.7.0 - version: 6.7.0(express@5.0.1) - express-session: - specifier: 1.17.3 - version: 1.17.3 - express-validator: - specifier: 6.14.1 - version: 6.14.1 - helmet: - specifier: 3.23.3 - version: 3.23.3 - helmet-csp: - specifier: 2.10.0 - version: 2.10.0 - joi: - specifier: 17.9.2 - version: 17.9.2 - joi-objectid: - specifier: 3.0.1 - version: 3.0.1 - jsonwebtoken: - specifier: 8.5.1 - version: 8.5.1 - lodash: - specifier: 4.17.21 - version: 4.17.21 - loopback: - specifier: 3.28.0 - version: 3.28.0 - loopback-boot: - specifier: 2.28.0 - version: 2.28.0 - loopback-connector-mongodb: - specifier: 5.6.0 - version: 5.6.0 - method-override: - specifier: 3.0.0 - version: 3.0.0 - moment: - specifier: 2.29.3 - version: 2.29.3 - moment-timezone: - specifier: 0.5.33 - version: 0.5.33 - mongodb: - specifier: 3.6.9 - version: 3.6.9 - morgan: - specifier: 1.10.0 - version: 1.10.0 - nanoid: - specifier: 3.3.4 - version: 3.3.4 - no-profanity: - specifier: ^1.4.2 - version: 1.5.1 - node-fetch: - specifier: ^2.6.7 - version: 2.7.0 - nodemailer-ses-transport: - specifier: 1.5.1 - version: 1.5.1 - passport: - specifier: 0.4.1 - version: 0.4.1 - passport-auth0: - specifier: 1.4.2 - version: 1.4.2(debug@2.2.0) - passport-local: - specifier: 1.0.0 - version: 1.0.0 - passport-mock-strategy: - specifier: 2.0.0 - version: 2.0.0 - query-string: - specifier: 6.14.0 - version: 6.14.0 - rate-limit-mongo: - specifier: ^2.3.2 - version: 2.3.2 - rx: - specifier: 4.1.0 - version: 4.1.0 - stripe: - specifier: 8.205.0 - version: 8.205.0 - strong-error-handler: - specifier: 3.5.0 - version: 3.5.0 - uuid: - specifier: 3.4.0 - version: 3.4.0 - validator: - specifier: 13.7.0 - version: 13.7.0 - devDependencies: - '@babel/cli': - specifier: 7.17.10 - version: 7.17.10(@babel/core@7.18.0) - '@babel/core': - specifier: 7.18.0 - version: 7.18.0 - '@babel/node': - specifier: 7.17.10 - version: 7.17.10(@babel/core@7.18.0) - '@babel/plugin-proposal-object-rest-spread': - specifier: 7.18.0 - version: 7.18.0(@babel/core@7.18.0) - '@babel/plugin-proposal-optional-chaining': - specifier: 7.17.12 - version: 7.17.12(@babel/core@7.18.0) - '@babel/preset-env': - specifier: 7.18.0 - version: 7.18.0(@babel/core@7.18.0) - loopback-component-explorer: - specifier: 6.4.0 - version: 6.4.0 - nodemon: - specifier: 2.0.16 - version: 2.0.16 - client: dependencies: '@babel/plugin-proposal-export-default-from': @@ -1630,13 +1461,6 @@ packages: resolution: {integrity: sha512-b9NGO6FKJeLGYnV7Z1yvcP1TNU4dkD5jNsLWOF1/sygZoASaQhNOlaiJ/1OH331YQ1R1oWk38nBb0frsYkDsOQ==} engines: {node: '>=18.0.0'} - '@babel/cli@7.17.10': - resolution: {integrity: sha512-OygVO1M2J4yPMNOW9pb+I6kFGpQK77HmG44Oz3hg8xQIl5L/2zq+ZohwAdSaqYgVwM0SfmPHZHphH4wR8qzVYw==} - engines: {node: '>=6.9.0'} - hasBin: true - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/code-frame@7.10.4': resolution: {integrity: sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==} @@ -1659,14 +1483,6 @@ packages: resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.22.20': - resolution: {integrity: sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.23.3': - resolution: {integrity: sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==} - engines: {node: '>=6.9.0'} - '@babel/compat-data@7.23.5': resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==} engines: {node: '>=6.9.0'} @@ -1675,10 +1491,6 @@ packages: resolution: {integrity: sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w==} engines: {node: '>=6.9.0'} - '@babel/core@7.18.0': - resolution: {integrity: sha512-Xyw74OlJwDijToNi0+6BBI5mLLR5+5R3bcSH80LXzjzEGEUlvNzujEE71BaD/ApEZHAvFI/Mlmp4M5lIkdeeWw==} - engines: {node: '>=6.9.0'} - '@babel/core@7.23.0': resolution: {integrity: sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==} engines: {node: '>=6.9.0'} @@ -1701,10 +1513,6 @@ packages: '@babel/core': ^7.11.0 eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 - '@babel/generator@7.23.0': - resolution: {integrity: sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==} - engines: {node: '>=6.9.0'} - '@babel/generator@7.23.3': resolution: {integrity: sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==} engines: {node: '>=6.9.0'} @@ -1733,10 +1541,6 @@ packages: resolution: {integrity: sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.22.15': - resolution: {integrity: sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==} - engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.23.6': resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} engines: {node: '>=6.9.0'} @@ -1753,11 +1557,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-define-polyfill-provider@0.3.3': - resolution: {integrity: sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==} - peerDependencies: - '@babel/core': ^7.4.0-0 - '@babel/helper-define-polyfill-provider@0.4.4': resolution: {integrity: sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==} peerDependencies: @@ -1792,12 +1591,6 @@ packages: resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.23.0': - resolution: {integrity: sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-module-transforms@7.23.3': resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} engines: {node: '>=6.9.0'} @@ -1883,10 +1676,6 @@ packages: resolution: {integrity: sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.23.1': - resolution: {integrity: sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==} - engines: {node: '>=6.9.0'} - '@babel/helpers@7.23.2': resolution: {integrity: sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==} engines: {node: '>=6.9.0'} @@ -1899,13 +1688,6 @@ packages: resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} engines: {node: '>=6.9.0'} - '@babel/node@7.17.10': - resolution: {integrity: sha512-sFFMyvw23U8QOcTnLJnw2/Myr01e4+iLVy7rHAHrNSnXAfnwS3j2NqihpmZm7TotyNKKf/y8cJ96T5asY46eyw==} - engines: {node: '>=6.9.0'} - hasBin: true - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/parser@7.23.0': resolution: {integrity: sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==} engines: {node: '>=6.0.0'} @@ -1936,24 +1718,12 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.22.15': - resolution: {integrity: sha512-FB9iYlz7rURmRJyXRKEnalYPPdn87H5no108cyuQQyMwlpJ2SJtpIUBI27kdTin956pz+LPypkPVPUTlxOmrsg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3': resolution: {integrity: sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.22.15': - resolution: {integrity: sha512-Hyph9LseGvAeeXzikV88bczhsrLrIZqDPxO+sSmAunMPaGrBGhfMWzCPYTtiW9t+HzSE2wtV8e5cc5P6r1xMDQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.13.0 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.23.3': resolution: {integrity: sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==} engines: {node: '>=6.9.0'} @@ -1966,13 +1736,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-proposal-async-generator-functions@7.20.7': - resolution: {integrity: sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead. - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-proposal-class-properties@7.17.12': resolution: {integrity: sha512-U0mI9q8pW5Q9EaTHFPwSVusPMV/DV9Mm8p7csqROFLtIE9rBF5piLqyrBGigftALrBcsBGu4m38JneAe7ZDLXw==} engines: {node: '>=6.9.0'} @@ -1980,53 +1743,18 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-proposal-class-static-block@7.21.0': - resolution: {integrity: sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-static-block instead. - peerDependencies: - '@babel/core': ^7.12.0 - - '@babel/plugin-proposal-dynamic-import@7.18.6': - resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-dynamic-import instead. - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-proposal-export-default-from@7.23.3': resolution: {integrity: sha512-Q23MpLZfSGZL1kU7fWqV262q65svLSCIP5kZ/JCW/rKTCm/FrLjpvEd2kfUYMVeHh4QhV/xzyoRAHWrAZJrE3Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-proposal-export-namespace-from@7.18.9': - resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead. - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-proposal-function-bind@7.23.3': resolution: {integrity: sha512-LlDuU9NIXn1JJugzvqWeEY4m/K/vJpIp93L2fA9tHqDVsIxezsit/sHrqJWbswWkzSIrKuuI8nF65Ewtka3k2g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-proposal-json-strings@7.18.6': - resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-json-strings instead. - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-proposal-logical-assignment-operators@7.20.7': - resolution: {integrity: sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead. - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6': resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} engines: {node: '>=6.9.0'} @@ -2047,20 +1775,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-proposal-object-rest-spread@7.18.0': - resolution: {integrity: sha512-nbTv371eTrFabDfHLElkn9oyf9VG+VKK6WMzhY2o4eHKaG19BToD9947zzGMO6I/Irstx9d8CwX6njPNIAR/yw==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead. - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-proposal-optional-catch-binding@7.18.6': - resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead. - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-proposal-optional-chaining@7.17.12': resolution: {integrity: sha512-7wigcOs/Z4YWlK7xxjkvaIw84vGhDv/P1dFGQap0nHkc8gFKY/r+hXc8Qzf5k1gY7CvGIcHqAnOagVKJJ1wVOQ==} engines: {node: '>=6.9.0'} @@ -2068,33 +1782,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-proposal-private-methods@7.18.6': - resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead. - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-proposal-private-property-in-object@7.21.11': - resolution: {integrity: sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead. - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-proposal-unicode-property-regex@7.18.6': - resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==} - engines: {node: '>=4'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead. - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-async-generators@7.8.4': resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: @@ -2138,12 +1831,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-assertions@7.22.5': - resolution: {integrity: sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-assertions@7.23.3': resolution: {integrity: sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==} engines: {node: '>=6.9.0'} @@ -2243,12 +1930,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-transform-arrow-functions@7.22.5': - resolution: {integrity: sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-arrow-functions@7.23.3': resolution: {integrity: sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==} engines: {node: '>=6.9.0'} @@ -2261,36 +1942,18 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-to-generator@7.22.5': - resolution: {integrity: sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-to-generator@7.23.3': resolution: {integrity: sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoped-functions@7.22.5': - resolution: {integrity: sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoped-functions@7.23.3': resolution: {integrity: sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoping@7.23.0': - resolution: {integrity: sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoping@7.23.4': resolution: {integrity: sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==} engines: {node: '>=6.9.0'} @@ -2309,12 +1972,6 @@ packages: peerDependencies: '@babel/core': ^7.12.0 - '@babel/plugin-transform-classes@7.22.15': - resolution: {integrity: sha512-VbbC3PGjBdE0wAWDdHM9G8Gm977pnYI0XpqMd6LrKISj8/DJXEsWqgRuTYaNE9Bv0JGhTZUzHDlMk18IpOuoqw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-classes@7.23.3': resolution: {integrity: sha512-FGEQmugvAEu2QtgtU0uTASXevfLMFfBeVCIIdcQhn/uBQsMTjBajdnAtanQlOcuihWh10PZ7+HWvc7NtBwP74w==} engines: {node: '>=6.9.0'} @@ -2327,48 +1984,24 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-computed-properties@7.22.5': - resolution: {integrity: sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-computed-properties@7.23.3': resolution: {integrity: sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-destructuring@7.23.0': - resolution: {integrity: sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-destructuring@7.23.3': resolution: {integrity: sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-dotall-regex@7.22.5': - resolution: {integrity: sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-dotall-regex@7.23.3': resolution: {integrity: sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-duplicate-keys@7.22.5': - resolution: {integrity: sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-duplicate-keys@7.23.3': resolution: {integrity: sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==} engines: {node: '>=6.9.0'} @@ -2381,12 +2014,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-exponentiation-operator@7.22.5': - resolution: {integrity: sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-exponentiation-operator@7.23.3': resolution: {integrity: sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==} engines: {node: '>=6.9.0'} @@ -2399,24 +2026,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-for-of@7.22.15': - resolution: {integrity: sha512-me6VGeHsx30+xh9fbDLLPi0J1HzmeIIyenoOQHuw2D4m2SAU3NrspX5XxJLBpqn5yrLzrlw2Iy3RA//Bx27iOA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-for-of@7.23.6': resolution: {integrity: sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-function-name@7.22.5': - resolution: {integrity: sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-function-name@7.23.3': resolution: {integrity: sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==} engines: {node: '>=6.9.0'} @@ -2429,12 +2044,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-literals@7.22.5': - resolution: {integrity: sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-literals@7.23.3': resolution: {integrity: sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==} engines: {node: '>=6.9.0'} @@ -2447,60 +2056,30 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-member-expression-literals@7.22.5': - resolution: {integrity: sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-member-expression-literals@7.23.3': resolution: {integrity: sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-amd@7.23.0': - resolution: {integrity: sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-amd@7.23.3': resolution: {integrity: sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-commonjs@7.23.0': - resolution: {integrity: sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-commonjs@7.23.3': resolution: {integrity: sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-systemjs@7.23.0': - resolution: {integrity: sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-systemjs@7.23.3': resolution: {integrity: sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-umd@7.22.5': - resolution: {integrity: sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-umd@7.23.3': resolution: {integrity: sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==} engines: {node: '>=6.9.0'} @@ -2513,12 +2092,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-transform-new-target@7.22.5': - resolution: {integrity: sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-new-target@7.23.3': resolution: {integrity: sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==} engines: {node: '>=6.9.0'} @@ -2543,12 +2116,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-object-super@7.22.5': - resolution: {integrity: sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-object-super@7.23.3': resolution: {integrity: sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==} engines: {node: '>=6.9.0'} @@ -2561,24 +2128,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-chaining@7.23.3': - resolution: {integrity: sha512-zvL8vIfIUgMccIAK1lxjvNv572JHFJIKb4MWBz5OGdBQA0fB0Xluix5rmOby48exiJc987neOmP/m9Fnpkz3Tg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-chaining@7.23.4': resolution: {integrity: sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-parameters@7.22.15': - resolution: {integrity: sha512-hjk7qKIqhyzhhUvRT683TYQOFa/4cQKwQy7ALvTpODswN40MljzNDa0YldevS6tGbxwaEKVn502JmY0dP7qEtQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-parameters@7.23.3': resolution: {integrity: sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==} engines: {node: '>=6.9.0'} @@ -2597,12 +2152,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-property-literals@7.22.5': - resolution: {integrity: sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-property-literals@7.23.3': resolution: {integrity: sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==} engines: {node: '>=6.9.0'} @@ -2669,24 +2218,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regenerator@7.22.10': - resolution: {integrity: sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-regenerator@7.23.3': resolution: {integrity: sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-reserved-words@7.22.5': - resolution: {integrity: sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-reserved-words@7.23.3': resolution: {integrity: sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==} engines: {node: '>=6.9.0'} @@ -2699,60 +2236,30 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-shorthand-properties@7.22.5': - resolution: {integrity: sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-shorthand-properties@7.23.3': resolution: {integrity: sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-spread@7.22.5': - resolution: {integrity: sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-spread@7.23.3': resolution: {integrity: sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-sticky-regex@7.22.5': - resolution: {integrity: sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-sticky-regex@7.23.3': resolution: {integrity: sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-template-literals@7.22.5': - resolution: {integrity: sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-template-literals@7.23.3': resolution: {integrity: sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typeof-symbol@7.22.5': - resolution: {integrity: sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typeof-symbol@7.23.3': resolution: {integrity: sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==} engines: {node: '>=6.9.0'} @@ -2765,12 +2272,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-escapes@7.22.10': - resolution: {integrity: sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-escapes@7.23.3': resolution: {integrity: sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==} engines: {node: '>=6.9.0'} @@ -2783,12 +2284,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-regex@7.22.5': - resolution: {integrity: sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-unicode-regex@7.23.3': resolution: {integrity: sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==} engines: {node: '>=6.9.0'} @@ -2801,23 +2296,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/preset-env@7.18.0': - resolution: {integrity: sha512-cP74OMs7ECLPeG1reiCQ/D/ypyOxgfm8uR6HRYV23vTJ7Lu1nbgj9DQDo/vH59gnn7GOAwtTDPPYV4aXzsMKHA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/preset-env@7.23.7': resolution: {integrity: sha512-SY27X/GtTz/L4UryMNJ6p4fH4nsgWbz84y9FE0bQeWJP6O5BhgVCt53CotQKHCOeXJel8VyhlhujhlltKms/CA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/preset-modules@0.1.6': - resolution: {integrity: sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg==} - peerDependencies: - '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 - '@babel/preset-modules@0.1.6-no-external-plugins': resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} peerDependencies: @@ -2841,12 +2325,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/register@7.22.15': - resolution: {integrity: sha512-V3Q3EqoQdn65RCgTLwauZaTfd1ShhwPmbBv+1dkZV/HpCGMKVyn6oFcRlI7RaKqiDQjX2Qd3AuoEguBgdjIKlg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/register@7.23.7': resolution: {integrity: sha512-EjJeB6+kvpk+Y5DAkEAmbOBEFkh9OASx0huoEkqYTFxAZHzOAX2Oh5uwAUuL2rUddqfM0SA+KPXV2TbzoZ2kvQ==} engines: {node: '>=6.9.0'} @@ -2884,10 +2362,6 @@ packages: resolution: {integrity: sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.23.3': - resolution: {integrity: sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==} - engines: {node: '>=6.9.0'} - '@babel/traverse@7.23.7': resolution: {integrity: sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==} engines: {node: '>=6.9.0'} @@ -2912,10 +2386,6 @@ packages: resolution: {integrity: sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==} engines: {node: '>=6.9.0'} - '@babel/types@7.23.6': - resolution: {integrity: sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==} - engines: {node: '>=6.9.0'} - '@babel/types@7.23.9': resolution: {integrity: sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==} engines: {node: '>=6.9.0'} @@ -3421,10 +2891,6 @@ packages: '@freecodecamp/loop-protect@3.0.0': resolution: {integrity: sha512-5zIULL5pm7Ylkk2dPq1f/4KJTAV5KQZEAFQg/qFj0t18EBSNO3fjn3HdfE1sPocXhXom3DVvZO3Rl2sqifMFYQ==} - '@freecodecamp/loopback-component-passport@1.2.0': - resolution: {integrity: sha512-4R5/ZdMCituYacObYYJV454IqrCSatM9A1BmTYMl7qNcwaXK4+XjUdjPeOWVliB2Qx2PTBQNC8pj5ZYpzC2SoQ==} - engines: {node: '>=6'} - '@freecodecamp/ui@4.0.1': resolution: {integrity: sha512-giT1LcKKvqaMkWJbaqhiSO7a3XUH+F5mCzGCIhQo7GtjMhqm0o3btAalNP38lGHa9LDdEwnszCdve8+/g7vOIw==} engines: {node: '>=20', pnpm: '9'} @@ -3841,9 +3307,6 @@ packages: resolution: {integrity: sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w==} engines: {node: '>=18'} - '@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3': - resolution: {integrity: sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==} - '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} @@ -4315,18 +3778,10 @@ packages: '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@sentry/core@7.37.1': - resolution: {integrity: sha512-eS5hoFDjAOl7POZg6K77J0oiypiqR1782oVSB49UkjK+D8tCZzZ5PxPMv0b/O0310p7x4oZ3WGRJaWEN3vY4KQ==} - engines: {node: '>=8'} - '@sentry/core@9.1.0': resolution: {integrity: sha512-djWEzSBpMgqdF3GQuxO+kXCUX+Mgq42G4Uah/HSUBvPDHKipMmyWlutGRoFyVPPOnCDgpHu3wCt83wbpEyVmDw==} engines: {node: '>=18'} - '@sentry/node@7.37.1': - resolution: {integrity: sha512-nGerngIo5JwinJgl7m0SaL/xI+YRBlhb53gbkuLSAAcnoitBFzbp7LjywsqYFTWuWDIyk7O2t124GNxtolBAgA==} - engines: {node: '>=8'} - '@sentry/node@9.1.0': resolution: {integrity: sha512-Xf9N0rpZ+lf3kA/MBa0yA7/wBd+dW8QhBav2YmM2GpqrrZ+3HtP6sT0N9+D3qADUYTn0RE2MNo8yaiaM6/FfPw==} engines: {node: '>=18'} @@ -4342,21 +3797,6 @@ packages: '@opentelemetry/sdk-trace-base': ^1.30.1 '@opentelemetry/semantic-conventions': ^1.28.0 - '@sentry/tracing@7.37.1': - resolution: {integrity: sha512-3mQG2XtMCGqDkgfzhKpRJAIfRaokNAOF8WafgAmFmZQwEDsRAFjZ3pLoO+KiBUeQE5E5et7HyWBOl9rqHCkWnQ==} - engines: {node: '>=8'} - - '@sentry/types@7.37.1': - resolution: {integrity: sha512-c2HWyWSgVA0V4+DSW2qVb0yjftrb1X/q2CzCom+ayjGHO72qyWC+9Tc+7ZfotU1mapRjqUWBgkXkbGmao8N8Ug==} - engines: {node: '>=8'} - - '@sentry/utils@7.37.1': - resolution: {integrity: sha512-/4mJOyDsfysx+5TXyJgSI+Ihw2/0EVJbrHjCyXPDXW5ADwbtU8VdBZ0unOmF0hk4QfftqwM9cyEu3BN4iBJsEA==} - engines: {node: '>=8'} - - '@sideway/address@4.1.4': - resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==} - '@sideway/address@4.1.5': resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} @@ -4957,9 +4397,6 @@ packages: '@types/express-serve-static-core@5.0.6': resolution: {integrity: sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==} - '@types/express@4.17.18': - resolution: {integrity: sha512-Sxv8BSLLgsBYmcnGdGjjEjqET2U+AKAdCRODmMiq02FgjwuV75Ut85DRpvFjyw/Mk0vgUOliGRU0UUmuuZHByQ==} - '@types/express@4.17.21': resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} @@ -5086,9 +4523,6 @@ packages: '@types/node-fetch@2.1.0': resolution: {integrity: sha512-7qhZIMCvHDJMZtdirrb/SkmTs2Dg8oVCLpUCOxNKm36xBdUZhh2JDWO/BeOdI5UyStyaCmeRv5UskaiH7kAgdg==} - '@types/node@10.17.60': - resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==} - '@types/node@14.18.63': resolution: {integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==} @@ -5119,9 +4553,6 @@ packages: '@types/parse5@5.0.3': resolution: {integrity: sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==} - '@types/passport@1.0.13': - resolution: {integrity: sha512-XXURryL+EZAWtbQFOHX1eNB+RJwz5XMPPz1xrGpEKr2xUZCXM4NCPkHMtZQ3B2tTSG/1IRaAcTHjczRA4sSFCw==} - '@types/pg-pool@2.0.6': resolution: {integrity: sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==} @@ -5577,9 +5008,6 @@ packages: abstract-logging@2.0.1: resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} - accept-language@3.0.18: - resolution: {integrity: sha512-sUofgqBPzgfcF20sPoBYGQ1IhQLt2LSkxTnlQSuLF3n5gPEqd5AimbvOvHEi0T1kLMiGVqPWzI5a9OteBRth3A==} - accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -5872,10 +5300,6 @@ packages: resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} engines: {node: '>= 0.4'} - array.prototype.reduce@1.0.6: - resolution: {integrity: sha512-UW+Mz8LG/sPSU8jRDCjVr6J/ZKAGpHfwrZ6kWTG5qCxIEiXdVshqGnu5vEZA8S1y6X4aCSbQZ0/EEsfvEvBiSg==} - engines: {node: '>= 0.4'} - array.prototype.tosorted@1.1.2: resolution: {integrity: sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==} @@ -5901,13 +5325,6 @@ packages: asn1.js@5.4.1: resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} - asn1@0.2.6: - resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} - - assert-plus@1.0.0: - resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} - engines: {node: '>=0.8'} - assert@1.5.1: resolution: {integrity: sha512-zzw1uCAgLbsKwBfFc8CX78DDg+xZeBksSO3vwVIDDN5i94eOrPsSSyiVhmsSABFDM/OcpE2aagCat9dnWQLG1A==} @@ -5942,18 +5359,9 @@ packages: async-retry-ng@2.0.1: resolution: {integrity: sha512-iitlc2murdQ3/A5Re3CcplQBEf7vOmFrFQ6RFn3+/+zZUyIHYkZnnEziMSa6YIb2Bs2EJEPZWReTxjHqvQbDbw==} - async@0.9.2: - resolution: {integrity: sha512-l6ToIJIotphWahxxHyzK9bnLR6kM4jJIIgLShZeqLY7iboHoGkdgFl7W2/Ivi4SkMJYGKqW8vSuk0uKUj6qsSw==} - async@1.5.2: resolution: {integrity: sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==} - async@2.6.4: - resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==} - - async@3.2.4: - resolution: {integrity: sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==} - asynciterator.prototype@1.0.0: resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==} @@ -5991,16 +5399,6 @@ packages: avvio@8.3.0: resolution: {integrity: sha512-VBVH0jubFr9LdFASy/vNtm5giTrnbVquWBhT0fyizuNK2rQ7e7ONU2plZQWUNqtE1EmxFEb+kbSkFRkstiaS9Q==} - aws-sdk@2.1467.0: - resolution: {integrity: sha512-XJNbV2ORQB6XanyBLPJBjvm6axWlblFdyFHa+ZaUK4Kp3cZBggy53+0PMxS3bAGJKcP6h2pZQio9xZMCoebAKQ==} - engines: {node: '>= 10.0.0'} - - aws-sign2@0.7.0: - resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} - - aws4@1.12.0: - resolution: {integrity: sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==} - axe-core@4.10.3: resolution: {integrity: sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg==} engines: {node: '>=4'} @@ -6012,9 +5410,6 @@ packages: axios@0.21.4: resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} - axios@0.22.0: - resolution: {integrity: sha512-Z0U3uhqQeg1oNcihswf4ZD57O3NrR1+ZXhxaROaWpDmsDTx7T2HNBV2ulBtie2hwJptu8UvgnJoK+BIqdzh/1w==} - axobject-query@3.2.1: resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} @@ -6069,11 +5464,6 @@ packages: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} - babel-plugin-polyfill-corejs2@0.3.3: - resolution: {integrity: sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - babel-plugin-polyfill-corejs2@0.4.7: resolution: {integrity: sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==} peerDependencies: @@ -6084,21 +5474,11 @@ packages: peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-corejs3@0.5.3: - resolution: {integrity: sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw==} - peerDependencies: - '@babel/core': ^7.0.0-0 - babel-plugin-polyfill-corejs3@0.8.7: resolution: {integrity: sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-regenerator@0.3.1: - resolution: {integrity: sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A==} - peerDependencies: - '@babel/core': ^7.0.0-0 - babel-plugin-polyfill-regenerator@0.5.5: resolution: {integrity: sha512-OJGYZlhLqBh2DDHeqAxWB1XIvr49CxiJ2gIt61/PU55CQK4Z58OzMqjDe1zwQdQk+rBYsRc+1rJmdajM3gimHg==} peerDependencies: @@ -6175,14 +5555,6 @@ packages: resolution: {integrity: sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==} engines: {node: '>= 0.6.0'} - base64-js@0.0.2: - resolution: {integrity: sha512-Pj9L87dCdGcKlSqPVUjD+q96pbIx1zQQLb2CUiWURfjiBELv84YX+0nGnKmyT/9KkC7PQk7UN1w+Al8bBozaxQ==} - engines: {node: '>= 0.4'} - - base64-js@1.0.2: - resolution: {integrity: sha512-ZXBDPMt/v/8fsIqn+Z5VwrhdR6jVka0bYobHdGia0Nxi7BJ9i/Uvml3AocHIBtIIBhZjBw5MR0aR4ROs/8+SNg==} - engines: {node: '>= 0.4'} - base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -6190,10 +5562,6 @@ packages: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} engines: {node: ^4.5.0 || >= 5.9} - base64url@3.0.1: - resolution: {integrity: sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==} - engines: {node: '>=6.0.0'} - base@0.11.2: resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} engines: {node: '>=0.10.0'} @@ -6209,13 +5577,6 @@ packages: batch@0.6.1: resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} - bcp47@1.1.2: - resolution: {integrity: sha512-JnkkL4GUpOvvanH9AZPX38CxhiLsXMBicBY2IAtqiVN8YulGDQybUydWA4W6yAMtw6iShtw+8HEF6cfrTHU+UQ==} - engines: {node: '>=0.10'} - - bcrypt-pbkdf@1.0.2: - resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} - bcryptjs@2.4.3: resolution: {integrity: sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==} @@ -6243,9 +5604,6 @@ packages: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} - bl@2.2.1: - resolution: {integrity: sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==} - bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -6273,18 +5631,9 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - bops@0.0.7: - resolution: {integrity: sha512-oF8JFj2vZoTTzbS4haaB/37vqoJbZXxPBWmNdFONu3dUBW+zp7JcoIIYYd1r+4/YwFM8QUSR1u4rrPbtcdHsRg==} - - bops@1.0.0: - resolution: {integrity: sha512-vVai54aP4LqbM+KNB1giwMo9nHvlV7pc7+iUNHYDTQe6WWI9L/jeSPBC89kUz3xA8qD7sZLldHxOXip1npWbmw==} - bowser@2.11.0: resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} - bowser@2.9.0: - resolution: {integrity: sha512-2ld76tuLBNFekRgmJfT2+3j5MIrP6bFict8WAIT3beq+srz1gcKNAdNKMqHqauQt63NmAa88HfP1/Ypa9Er3HA==} - boxen@4.2.0: resolution: {integrity: sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==} engines: {node: '>=8'} @@ -6387,10 +5736,6 @@ packages: bson-objectid@2.0.4: resolution: {integrity: sha512-vgnKAUzcDoa+AeyYwXCoHyF2q6u/8H46dxu5JN+4/TZeq/Dlinn0K6GvxsCLb3LHUJl0m/TLiEK31kUwtgocMQ==} - bson@1.1.6: - resolution: {integrity: sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==} - engines: {node: '>=0.6.19'} - bson@6.9.0: resolution: {integrity: sha512-X9hJeyeM0//Fus+0pc5dSUMhhrrmWwQUtdavaQeF3Ta6m69matZkGWV/MrBcnwUeLC8W9kwwc2hfkZgUuCX3Ig==} engines: {node: '>=16.20.1'} @@ -6408,9 +5753,6 @@ packages: buffer-xor@1.0.3: resolution: {integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==} - buffer@4.9.2: - resolution: {integrity: sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==} - buffer@5.2.1: resolution: {integrity: sha512-c+Ko0loDaFfuPWiL02ls9Xd3GO3cPVmUobQ6t3rXNUk304u6hGq+8N/kFi+QEIKhzK3uwolVhLzszmfLmMLnqg==} @@ -6499,9 +5841,6 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - camelize@1.0.0: - resolution: {integrity: sha512-W2lPwkBkMZwFlPCXhIlYgxu+7gC/NUlCtdK652DAJ1JdgV0sTrvuPFshNPrFa1TY2JOkLhgdeEBplB4ezEa+xg==} - caniuse-api@3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} @@ -6511,15 +5850,9 @@ packages: caniuse-lite@1.0.30001589: resolution: {integrity: sha512-vNQWS6kI+q6sBlHbh71IIeC+sRwK2N3EDySc/updIGhIee2x5z00J4c1242/5/d6EpEMdOnk/m+6tuk4/tcsqg==} - canonical-json@0.0.4: - resolution: {integrity: sha512-2sW7x0m/P7dqEnO0O87U7RTVQAaa7MELcd+Jd9FA6CYgYtwJ1TlDWIYMD8nuMkH1KoThsJogqgLyklrt9d/Azw==} - canvas-confetti@1.6.0: resolution: {integrity: sha512-ej+w/m8Jzpv9Z7W7uJZer14Ke8P2ogsjg4ZMGIuq4iqUOqY2Jq8BNW42iGmNfRwREaaEfFIczLuZZiEVSYNHAA==} - caseless@0.12.0: - resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} - ccount@1.1.0: resolution: {integrity: sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==} @@ -6577,9 +5910,6 @@ packages: chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} - charenc@0.0.2: - resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} - check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} @@ -6630,9 +5960,6 @@ packages: classnames@2.3.2: resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==} - cldrjs@0.5.5: - resolution: {integrity: sha512-KDwzwbmLIPfCgd8JERVDpQKrUUM1U4KpFJJg2IROv89rF172lLufoJnqJ/Wea6fXL5bO6WjuLMzY8V52UWPvkA==} - clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -6759,10 +6086,6 @@ packages: commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} @@ -6811,15 +6134,6 @@ packages: confusing-browser-globals@1.0.11: resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} - connect-flash@0.1.1: - resolution: {integrity: sha512-2rcfELQt/ZMP+SM/pG8PyhJRaLKp+6Hk2IUBNkEit09X+vwn3QsAL3ZbYtxUn7NVPzbMTSLRDhqe0B/eh30RYA==} - engines: {node: '>= 0.4.0'} - - connect-mongo@3.2.0: - resolution: {integrity: sha512-0Mx88079Z20CG909wCFlR3UxhMYGg6Ibn1hkIje1hwsqOLWtL9HJV+XD0DAjUvQScK6WqY/FA8tSVQM9rR64Rw==} - peerDependencies: - express-session: ^1.17.1 - connect@3.7.0: resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} engines: {node: '>= 0.10.0'} @@ -6842,10 +6156,6 @@ packages: resolution: {integrity: sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==} engines: {node: '>= 0.6'} - content-security-policy-builder@2.1.0: - resolution: {integrity: sha512-/MtLWhJVvJNkA9dVLAp6fg9LxD2gfI6R2Fi1hPmfjYXSahJJzcfvoeDOxSyp4NvxMuwWv3WMssE9o31DoULHrQ==} - engines: {node: '>=4.0.0'} - content-type@1.0.5: resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} engines: {node: '>= 0.6'} @@ -6871,10 +6181,6 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie-parser@1.4.6: - resolution: {integrity: sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==} - engines: {node: '>= 0.8.0'} - cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} @@ -6882,14 +6188,6 @@ packages: resolution: {integrity: sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==} engines: {node: '>=6.6.0'} - cookie@0.4.0: - resolution: {integrity: sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==} - engines: {node: '>= 0.6'} - - cookie@0.4.1: - resolution: {integrity: sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==} - engines: {node: '>= 0.6'} - cookie@0.4.2: resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} engines: {node: '>= 0.6'} @@ -6919,9 +6217,6 @@ packages: peerDependencies: webpack: ^5.1.0 - core-js-compat@3.33.0: - resolution: {integrity: sha512-0w4LcLXsVEuNkIqwjjf9rjCoPhK8uqA4tMRh4Ge26vfLtUutshn+aRJU21I9LCJlh2QQHfisNToLjw1XEJLTWw==} - core-js-compat@3.36.0: resolution: {integrity: sha512-iV9Pd/PsgjNWBXeq8XRtWVSgz2tKAfhfvBs7qxYty+RlRd+OCksaWmOnc4JKrTc1cToXL1N0s3l/vwlxPtdElw==} @@ -6935,9 +6230,6 @@ packages: core-js@3.33.0: resolution: {integrity: sha512-HoZr92+ZjFEKar5HS6MC776gYslNOKHt75mEBKWKnPeFDpZ6nH5OeF3S6HFT1mUAUZKrzkez05VboaX8myjSuw==} - core-util-is@1.0.2: - resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} - core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -7005,9 +6297,6 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - crypt@0.0.2: - resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} - crypto-browserify@3.12.0: resolution: {integrity: sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==} @@ -7015,10 +6304,6 @@ packages: resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==} engines: {node: '>=8'} - csrf@3.1.0: - resolution: {integrity: sha512-uTqEnCvWRk042asU6JtapDTcJeeailFy4ydOQS28bj1hcLnYRiqi8SsD2jS412AY1I/4qdOwWZun774iqywf9w==} - engines: {node: '>= 0.8'} - css-declaration-sorter@6.4.1: resolution: {integrity: sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==} engines: {node: ^10 || ^12 || >=14} @@ -7121,11 +6406,6 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - csurf@1.11.0: - resolution: {integrity: sha512-UCtehyEExKTxgiu8UHdGvHj4tnpE/Qctue03Giq5gPgMQ9cg/ciod5blZQ5a4uCEenNQjxyGuzygLdKUmee/bQ==} - engines: {node: '>= 0.8.0'} - deprecated: Please use another csrf package - d@1.0.1: resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==} @@ -7135,13 +6415,6 @@ packages: dash-ast@1.0.0: resolution: {integrity: sha512-Vy4dx7gquTeMcQR/hDkYLGUnwVil6vk4FOOct+djUnHOUWt+zJPJAaRIXaAFkPXtJjvlY7o3rfRu0/3hpnwoUA==} - dashdash@1.14.1: - resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} - engines: {node: '>=0.10'} - - dasherize@2.0.0: - resolution: {integrity: sha512-APql/TZ6FdLEpf2z7/X2a2zyqK8juYtqaSVqxw9mYoQ64CXkfU15AeLh8pUszT8+fnYjgm6t0aIYpWKJbnLkuA==} - data-uri-to-buffer@6.0.2: resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} engines: {node: '>= 14'} @@ -7169,9 +6442,6 @@ packages: dataloader@2.0.0: resolution: {integrity: sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ==} - date-fns@1.30.1: - resolution: {integrity: sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==} - date-fns@2.30.0: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} @@ -7198,14 +6468,6 @@ packages: supports-color: optional: true - debug@3.1.0: - resolution: {integrity: sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -7276,9 +6538,6 @@ packages: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} - dedent@0.7.0: - resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} - dedent@1.5.1: resolution: {integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==} peerDependencies: @@ -7366,10 +6625,6 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - denque@1.5.1: - resolution: {integrity: sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==} - engines: {node: '>=0.10'} - depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} @@ -7540,10 +6795,6 @@ packages: domutils@3.1.0: resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} - dont-sniff-mimetype@1.1.0: - resolution: {integrity: sha512-ZjI4zqTaxveH2/tTlzS1wFp+7ncxNZaIEWYg3lzZRHkKf5zPT/MnEG6WL0BhHMJUabkh8GeU5NL5j+rEUCb7Ug==} - engines: {node: '>=4.0.0'} - dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} @@ -7560,10 +6811,6 @@ packages: resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} engines: {node: '>=12'} - dotenv@6.2.0: - resolution: {integrity: sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==} - engines: {node: '>=6'} - dotenv@8.6.0: resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} engines: {node: '>=10'} @@ -7572,42 +6819,24 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - duplex@1.0.0: - resolution: {integrity: sha512-6Urdl3FU6TU6TAbd9b46YsvYhxqWvuuvlDL1VaP4DJb9E1jbU9Y5E6KUIXt7+0CUgKhPveZ495kqVAzm/uynyg==} - duplexer2@0.1.4: resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} duplexer3@0.1.5: resolution: {integrity: sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==} - duplexer@0.0.4: - resolution: {integrity: sha512-nO0WWuIDTde3CWK/8IPpG50dyhUilgpsqzYSIP+w20Yh+4iDgb/2Gs75QItcp0Hmx/JtxtTXBalj+LSTD1VemA==} - duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - ecc-jsbn@0.1.2: - resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} - ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - ejs@2.7.4: - resolution: {integrity: sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==} - engines: {node: '>=0.10.0'} - - ejs@3.1.9: - resolution: {integrity: sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==} - engines: {node: '>=0.10.0'} - hasBin: true - electron-to-chromium@1.4.622: resolution: {integrity: sha512-GZ47DEy0Gm2Z8RVG092CkFvX7SdotG57c4YZOe8W8qD4rOmk3plgeNmiLVRHP/Liqj1wRiY3uUUod9vb9hnxZA==} @@ -7780,12 +7009,6 @@ packages: es6-object-assign@1.1.0: resolution: {integrity: sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==} - es6-promise@4.2.8: - resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} - - es6-promisify@5.0.0: - resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==} - es6-symbol@3.1.3: resolution: {integrity: sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==} @@ -8129,19 +7352,12 @@ packages: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} - eventemitter2@5.0.1: - resolution: {integrity: sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg==} - eventemitter3@3.1.2: resolution: {integrity: sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==} eventemitter3@4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - events@1.1.1: - resolution: {integrity: sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==} - engines: {node: '>=0.4.x'} - events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -8153,10 +7369,6 @@ packages: resolution: {integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==} engines: {node: '>=6'} - execa@4.1.0: - resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} - engines: {node: '>=10'} - execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -8185,10 +7397,6 @@ packages: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - express-flash@0.0.2: - resolution: {integrity: sha512-QVUR0ZZRCaa8+iPHoUQaQJrQWcQuK/Q+19M7IUIdIEtvwhrA/ifHT7y1CVJI41YfGiOQnbGtn3uvd2vOdgu58A==} - engines: {node: '>= 0.8.0'} - express-graphql@0.12.0: resolution: {integrity: sha512-DwYaJQy0amdy3pgNtiTDuGGM2BLdj+YO2SgbKoLliCfuHv3VVTt7vNG/ZqK2hRYjtYHE2t2KB705EU94mE64zg==} engines: {node: '>= 10.x'} @@ -8196,20 +7404,6 @@ packages: peerDependencies: graphql: ^14.7.0 || ^15.3.0 - express-rate-limit@6.7.0: - resolution: {integrity: sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==} - engines: {node: '>= 12.9.0'} - peerDependencies: - express: ^4 || ^5 - - express-session@1.17.3: - resolution: {integrity: sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==} - engines: {node: '>= 0.8.0'} - - express-validator@6.14.1: - resolution: {integrity: sha512-4w7gn/jPW1a+r833xBqpu4pL7XiiScDwlbBIMtiqUEt/MVNqR94HOHyKLcCtnqCnEPiqrX1Mqt9l/SVN/iqeLA==} - engines: {node: '>= 8.0.0'} - express@4.18.2: resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} engines: {node: '>= 0.10.0'} @@ -8249,14 +7443,6 @@ packages: engines: {node: '>= 10.17.0'} hasBin: true - extsprintf@1.3.0: - resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} - engines: {'0': node >=0.6.0} - - eyes@0.1.8: - resolution: {integrity: sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==} - engines: {node: '> 0.1.90'} - fast-content-type-parse@1.1.0: resolution: {integrity: sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==} @@ -8352,10 +7538,6 @@ packages: fd@0.0.3: resolution: {integrity: sha512-iAHrIslQb3U68OcMSP0kkNWabp7sSN6d2TBSb2JO3gcLJVDd4owr/hKM4SFJovFOUeeXeItjYgouEDTMWiVAnA==} - feature-policy@0.3.0: - resolution: {integrity: sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ==} - engines: {node: '>=4.0.0'} - figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -8381,9 +7563,6 @@ packages: resolution: {integrity: sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==} engines: {node: '>=10'} - filelist@1.0.4: - resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} - filesize@6.1.0: resolution: {integrity: sha512-LpCHtPQ3sFx67z+uh2HnSyWSLLu5Jxo21795uRDuar/EOuYWXib5EmPaGIBuSnRqH2IODiKA2k5re/K9OnN/Yg==} engines: {node: '>= 0.4.0'} @@ -8481,9 +7660,6 @@ packages: resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} engines: {node: '>=0.10.0'} - forever-agent@0.6.1: - resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} - fork-ts-checker-webpack-plugin@4.1.6: resolution: {integrity: sha512-DUxuQaKoqfNne8iikd14SAkh5uw4+8vNifp6gmA73yYNS6ywLIWSLD/n/mBzHQRpW3J7rbATEakmiA8JvkTyZw==} engines: {node: '>=6.11.5', yarn: '>=1.0.0'} @@ -8498,10 +7674,6 @@ packages: vue-template-compiler: optional: true - form-data@2.3.3: - resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} - engines: {node: '>= 0.12'} - form-data@3.0.1: resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} engines: {node: '>= 6'} @@ -8560,9 +7732,6 @@ packages: fs-monkey@1.0.5: resolution: {integrity: sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==} - fs-readdir-recursive@1.1.0: - resolution: {integrity: sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==} - fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -8803,9 +7972,6 @@ packages: resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} engines: {node: '>=0.10.0'} - getpass@0.1.7: - resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} - git-up@4.0.5: resolution: {integrity: sha512-YUvVDg/vX3d0syBsk/CKUTib0srcQME0JyHkL5BaYdwLsiCslPWmDSi8PUMo9pXYjrryMcmsCoCgsTpSCJEQaA==} @@ -8847,9 +8013,6 @@ packages: resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} engines: {node: '>=6'} - globalize@1.7.0: - resolution: {integrity: sha512-faR46vTIbFCeAemyuc9E6/d7Wrx9k2ae2L60UhakztFg6VuE42gENVJNuPFtt7Sdjrk9m2w8+py7Jj+JTNy59w==} - globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} engines: {node: '>=4'} @@ -8973,15 +8136,6 @@ packages: resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} engines: {node: '>=10'} - har-schema@2.0.0: - resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} - engines: {node: '>=4'} - - har-validator@5.1.5: - resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} - engines: {node: '>=6'} - deprecated: this library is no longer supported - harmony-reflect@1.6.2: resolution: {integrity: sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==} @@ -9110,18 +8264,6 @@ packages: headers-polyfill@4.0.3: resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} - helmet-crossdomain@0.4.0: - resolution: {integrity: sha512-AB4DTykRw3HCOxovD1nPR16hllrVImeFp5VBV9/twj66lJ2nU75DP8FPL0/Jp4jj79JhTfG+pFI2MD02kWJ+fA==} - engines: {node: '>=4.0.0'} - - helmet-csp@2.10.0: - resolution: {integrity: sha512-Rz953ZNEFk8sT2XvewXkYN0Ho4GEZdjAZy4stjiEQV3eN7GDxg1QKmYggH7otDyIA7uGA6XnUMVSgeJwbR5X+w==} - engines: {node: '>=4.0.0'} - - helmet@3.23.3: - resolution: {integrity: sha512-U3MeYdzPJQhtvqAVBPntVgAvNSOJyagwZwyKsFdyRa8TV3pOKVFljalPOCxbw5Wwf2kncGhmP0qHjyazIdNdSA==} - engines: {node: '>=4.0.0'} - help-me@4.2.0: resolution: {integrity: sha512-TAOnTB8Tz5Dw8penUuzHVrKNKlCIbwwbHnXraNJxPwf8LRtE2HlM84RYuezMFcwOJmoYOCWVDyJ8TQGxn9PgxA==} @@ -9133,10 +8275,6 @@ packages: resolution: {integrity: sha512-om8L9O5XwqeSdwl5NtHgrzK3wcF4fT9T4gb/NktoH8EyoZipas/tvUZLV48xT7fQfMYr9qvb0WEutqdf0LWSqA==} hasBin: true - hide-powered-by@1.1.0: - resolution: {integrity: sha512-Io1zA2yOA1YJslkr+AJlWSf2yWFkKjvkcL9Ni1XSUqnGLr/qRQe2UI3Cn/J9MsJht7yEVCe0SscY1HgVMujbgg==} - engines: {node: '>=4.0.0'} - highlight.js@10.7.3: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} @@ -9150,10 +8288,6 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - homedir-polyfill@1.0.3: - resolution: {integrity: sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==} - engines: {node: '>=0.10.0'} - hookified@1.7.1: resolution: {integrity: sha512-OXcdHsXeOiD7OJ5zvWj8Oy/6RCdLwntAX+wUrfemNcMGn6sux4xbEHi2QXwqePYhjQ/yvxxq2MvCRirdlHscBw==} @@ -9164,13 +8298,6 @@ packages: resolution: {integrity: sha512-aXpmwoOhRBrw6X3j0h5RloK4x1OzsxMPyxqIHyNfSe2pypkVTZFpEiRoSipPEPlMrh0HW/XsjkJ5WgnCirpNUw==} engines: {node: '>=10'} - hpkp@2.0.0: - resolution: {integrity: sha512-TaZpC6cO/k3DFsjfzz1LnOobbVSq+J+7WpJxrVtN4L+8+BPQj8iBDRB2Dx49613N+e7/+ZSQ9ra+xZm7Blf4wg==} - - hsts@2.2.0: - resolution: {integrity: sha512-ToaTnQ2TbJkochoVcdXYm4HOCliNozlviNsg+X2XQLQvZNI/kCHR9rZxVYpJB3UPcHz80PgxRyWQ7PdU1r+VBQ==} - engines: {node: '>=4.0.0'} - htm@3.1.1: resolution: {integrity: sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==} @@ -9232,10 +8359,6 @@ packages: resolution: {integrity: sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==} engines: {node: '>= 0.6'} - http-errors@1.7.3: - resolution: {integrity: sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==} - engines: {node: '>= 0.6'} - http-errors@1.8.0: resolution: {integrity: sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A==} engines: {node: '>= 0.6'} @@ -9263,26 +8386,10 @@ packages: resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} engines: {node: '>=8.0.0'} - http-signature@1.2.0: - resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} - engines: {node: '>=0.8', npm: '>=1.3.7'} - - http-status@1.7.0: - resolution: {integrity: sha512-6HZ8T2ywZKtNKOrRA22x4Z+fK+UiWzimWYSTROVHrZ46RX+hKsg9wCQiodRtfNrKfsvOkwsXA6R9q+TmDY+8nQ==} - engines: {node: '>= 0.4.0'} - http2-wrapper@1.0.3: resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} engines: {node: '>=10.19.0'} - httpntlm@1.6.1: - resolution: {integrity: sha512-Tcz3Ct9efvNqw3QdTl3h6IgRRlIQxwKkJELN/aAIGnzi2xvb3pDHdnMs8BrxWLV6OoT4DlVyhzSVhFt/tk0lIw==} - engines: {node: '>=0.8.0'} - - httpreq@1.1.1: - resolution: {integrity: sha512-uhSZLPPD2VXXOSN8Cni3kIsoFHaU2pT/nySEU/fHr/ePbqHYr0jeiQRmUKLEirC09SFPsdMoA7LU7UXMd/w0Kw==} - engines: {node: '>= 6.15.1'} - https-browserify@1.0.0: resolution: {integrity: sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==} @@ -9294,10 +8401,6 @@ packages: resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} engines: {node: '>= 14'} - human-signals@1.1.1: - resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} - engines: {node: '>=8.12.0'} - human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -9342,15 +8445,9 @@ packages: resolution: {integrity: sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==} engines: {node: '>=4'} - ieee754@1.1.13: - resolution: {integrity: sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==} - ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - ignore-by-default@1.0.1: - resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} - ignore@4.0.6: resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} engines: {node: '>= 4'} @@ -9405,10 +8502,6 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} - inflection@1.13.4: - resolution: {integrity: sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==} - engines: {'0': node >= 0.4.0} - inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -9471,14 +8564,6 @@ packages: invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} - invert-kv@2.0.0: - resolution: {integrity: sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==} - engines: {node: '>=4'} - - invert-kv@3.0.1: - resolution: {integrity: sha512-CYdFeFexxhv/Bcny+Q0BfOV+ltRlJcd4BBZBYFX/O0u4npJrgZtIcjokegtiSMAvlMTJ+Koq0GBCc//3bueQxw==} - engines: {node: '>=8'} - ip-address@9.0.5: resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==} engines: {node: '>= 12'} @@ -9925,10 +9010,6 @@ packages: isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - isemail@3.2.0: - resolution: {integrity: sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==} - engines: {node: '>=4.0.0'} - isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -9945,9 +9026,6 @@ packages: peerDependencies: ws: '*' - isstream@0.1.2: - resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} - istanbul-lib-coverage@3.2.0: resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} engines: {node: '>=8'} @@ -9982,15 +9060,6 @@ packages: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} - jake@10.8.7: - resolution: {integrity: sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==} - engines: {node: '>=10'} - hasBin: true - - jayson@2.1.2: - resolution: {integrity: sha512-2GejcQnEV35KYTXoBvzALIDdO/1oyEIoJHBnaJFhJhcurv0x2JqUXQW6xlDUhcNOpN9t+d2w+JGA6vOphb+5mg==} - hasBin: true - jest-changed-files@29.7.0: resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -10164,19 +9233,12 @@ packages: node-notifier: optional: true - jmespath@0.16.0: - resolution: {integrity: sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==} - engines: {node: '>= 0.6.0'} - joi-objectid@3.0.1: resolution: {integrity: sha512-V/3hbTlGpvJ03Me6DJbdBI08hBTasFOmipsauOsxOSnsF1blxV537WTl1zPwbfcKle4AK0Ma4OPnzMH4LlvTpQ==} joi@17.12.2: resolution: {integrity: sha512-RonXAIzCiHLc8ss3Ibuz45u28GOsWE1UpfDXLbN/9NKbL4tCJf8TWYVKsoYuuh+sAUt7fsSNpA+r2+TBA6Wjmw==} - joi@17.9.2: - resolution: {integrity: sha512-Itk/r+V4Dx0V3c7RLFdRh12IOjySm2/WGPMubBT92cQvRfYZhPM2W0hZlctjj72iES8jsRCwp7S/cRmWBnJ4nw==} - joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} @@ -10199,15 +9261,6 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - js2xmlparser@3.0.0: - resolution: {integrity: sha512-CSOkdn0/GhRFwxnipmhXfqJ+FG6+wkWBi46kKSsPx6+j65176ZiQcrCYpg6K8x3iLbO4k3zScBnZ7I/L80dAtw==} - - js2xmlparser@4.0.2: - resolution: {integrity: sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==} - - jsbn@0.1.1: - resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} - jsbn@1.1.0: resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==} @@ -10247,9 +9300,6 @@ packages: engines: {node: '>=6'} hasBin: true - json-buffer@2.0.11: - resolution: {integrity: sha512-Wu4/hxSZX7Krzjor+sZHWaRau6Be4WQHlrkl3v8cmxRBBewF2TotlgHUedKQJyFiUyFxnK/ZlRYnR9UNVZ7pkg==} - json-buffer@3.0.0: resolution: {integrity: sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==} @@ -10272,15 +9322,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - json-schema@0.4.0: - resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} - json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true @@ -10297,18 +9341,10 @@ packages: resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} engines: {'0': node >= 0.2.0} - jsonwebtoken@8.5.1: - resolution: {integrity: sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==} - engines: {node: '>=4', npm: '>=1.4.28'} - jsonwebtoken@9.0.2: resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} engines: {node: '>=12', npm: '>=6'} - jsprim@1.4.2: - resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} - engines: {node: '>=0.6.0'} - jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -10382,14 +9418,6 @@ packages: resolution: {integrity: sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==} engines: {node: '>=8'} - lcid@2.0.0: - resolution: {integrity: sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==} - engines: {node: '>=6'} - - lcid@3.1.1: - resolution: {integrity: sha512-M6T051+5QCGLBQb8id3hdvIW8+zeFV2FyBGFS9IEK5H9Wt4MueD4bW1eWikpHgZp+5xR3l5c8pZUkQsIA0BFZg==} - engines: {node: '>=8'} - leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -10575,55 +9603,6 @@ packages: longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} - loopback-boot@2.28.0: - resolution: {integrity: sha512-DTZnoWEMukgG2PrtguJ0Xk9HmIlHgcynGoxkDPa9oFiJ7l+8v92Ym8q7RQxOf8Nqws1kykPQ7PNuia+9AoeC7w==} - engines: {node: '>=6'} - deprecated: This version is no longer supported, please upgrade to 3.x - - loopback-component-explorer@6.4.0: - resolution: {integrity: sha512-vDRR4gqkvGOEXh5yL383xGuGxUW9xtF+NCY6/lJu1VAgupKltZxEx3Vw+L3nsGvQrlkJTSmiK3jk72qxkoBtbw==} - engines: {node: '>=6'} - - loopback-connector-mongodb@5.6.0: - resolution: {integrity: sha512-mhuOUknJkmZhr9BhJCUz3ggRuFKNZuU1tkZrwqUxwosAUPWeZMIdrj7XP9Vr6OCP6+RfZoSRMITj3keAOPTKrg==} - engines: {node: '>=10'} - - loopback-connector-remote@3.4.1: - resolution: {integrity: sha512-O22X2Gcq8YzZF9DvRjOCyktQlASw1/22i/zzqxJHNKSQA5aQYeTB0w5FttOiKxcw6Q/jzL476hUvUE/NaZVZ1Q==} - engines: {node: '>=6'} - - loopback-connector@4.11.1: - resolution: {integrity: sha512-EA31zur3xIhP4UW+P2rWEcSbqpk4jPddpTBZSSw8KCszM7T0/Pe4HvEmG0MndAWJctRPtrwKDEu/8rWuMDLf+A==} - engines: {node: '>=8.9'} - - loopback-connector@5.3.3: - resolution: {integrity: sha512-ZYULfy5W7+R2A3I9TILWZOdfMVcZ2qEQT/tye0Fy7Ju3zQ6Gv1bmroARGPGVDAneFt+5YaiaieLdoJ1t02hLpg==} - engines: {node: '>=10'} - - loopback-datasource-juggler@3.36.1: - resolution: {integrity: sha512-6eop3qxFyN3AkPBPUte2DHcsW1DopJwXXA20x3vwYsBSo4hLSv4gIeXo0+yqdQoXpHfbKRB9cv1hHEHAQSiWUA==} - engines: {node: '>=8'} - - loopback-datatype-geopoint@1.0.0: - resolution: {integrity: sha512-MqcEBXl/x4YC/hm/5ZRFBZGI9RCqHdy8zrv3jGHiE4cOnSdKVdranG+zEs8Xv7Z2sy/rV6qY3wsr7gBNcC9Kmw==} - engines: {node: '>=4'} - - loopback-filters@1.1.1: - resolution: {integrity: sha512-p0qSzuuX7eATe5Bxy+RqCj3vSfSFfdCtqyf3yuC+DpchMvgal33XlhEi2UmywyK/Ym28oVnZxxWmfrwFMzSwLQ==} - engines: {node: '>=4.0.0'} - - loopback-phase@3.4.0: - resolution: {integrity: sha512-FHtCOXO9IpaGkg/dw3lBQc2EmEtUx6LXZ0th5vkL1+jwDQVh6kdfvVk7wqVfZsskdOZz3j34rGWEP8qWx9JF0A==} - engines: {node: '>=8.9'} - - loopback-swagger@5.9.0: - resolution: {integrity: sha512-E4UnLu1H76w6R978AhAfwOATMHVuQRbar4P29asox3ev+BXUVHBcaRboRQvDJfX2UvUC54O5BFNYGzstkARukA==} - engines: {node: '>=8'} - - loopback@3.28.0: - resolution: {integrity: sha512-txYAc2vUn2imOKqcxnRFTm7fLx6+dbZ+V/wfAME0kyOJVyuV56H8RPpHl9/LTpKyNYQuoedGYrl9bwSavXgKoQ==} - engines: {node: '>=8'} - loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -10663,9 +9642,6 @@ packages: lru-queue@0.1.0: resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==} - lru_map@0.3.3: - resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==} - lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -10739,9 +9715,6 @@ packages: md5.js@1.3.5: resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} - md5@2.3.0: - resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} - mdast-builder@1.1.1: resolution: {integrity: sha512-a3KBk/LmYD6wKsWi8WJrGU/rXR4yuF4Men0JO0z6dSZCm5FrXXWTRDjqK0vGSqa+1M6p9edeuypZAZAzSehTUw==} @@ -10849,14 +9822,6 @@ packages: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} engines: {node: '>= 0.8'} - mem@4.3.0: - resolution: {integrity: sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==} - engines: {node: '>=6'} - - mem@5.1.1: - resolution: {integrity: sha512-qvwipnozMohxLXG1pOqoLiZKNkC4r4qqRucSoDwXowsNGDSULiqFTRUF05vcZWnwJSG22qTsynQhxbaMtnX9gw==} - engines: {node: '>=8'} - mem@8.1.1: resolution: {integrity: sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA==} engines: {node: '>=10'} @@ -10905,10 +9870,6 @@ packages: '@types/node': optional: true - method-override@3.0.0: - resolution: {integrity: sha512-IJ2NNN/mSl9w3kzWB92rcdHpz+HjkxhDJWNDBqSlas+zQdP8wBiJzITPg08M/k2uVvMow7Sk41atndNtt/PHSA==} - engines: {node: '>= 0.10'} - methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} @@ -11223,11 +10184,6 @@ packages: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - mocha@10.3.0: resolution: {integrity: sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==} engines: {node: '>= 14.0.0'} @@ -11245,9 +10201,6 @@ packages: module-details-from-path@1.0.3: resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} - moment-timezone@0.5.33: - resolution: {integrity: sha512-PTc2vcT8K9J5/9rDEPe5czSIKgLoGsH8UNpA4qZTVw0Vd/Uz19geE9abbIOQKaAQFcnQ3v5YEXrbSc5BpshH+w==} - moment@2.29.3: resolution: {integrity: sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==} @@ -11263,30 +10216,6 @@ packages: mongodb-connection-string-url@3.0.1: resolution: {integrity: sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==} - mongodb@3.6.9: - resolution: {integrity: sha512-1nSCKgSunzn/CXwgOWgbPHUWOO5OfERcuOWISmqd610jn0s8BU9K4879iJVabqgpPPbA6hO7rG48eq+fGED3Mg==} - engines: {node: '>=4'} - peerDependencies: - aws4: '*' - bson-ext: '*' - kerberos: '*' - mongodb-client-encryption: '*' - mongodb-extjson: '*' - snappy: '*' - peerDependenciesMeta: - aws4: - optional: true - bson-ext: - optional: true - kerberos: - optional: true - mongodb-client-encryption: - optional: true - mongodb-extjson: - optional: true - snappy: - optional: true - mongodb@6.10.0: resolution: {integrity: sha512-gP9vduuYWb9ZkDM546M+MP2qKVk5ZG2wPF63OvSRuUbqCR+11ZCAE1mOfllhlAG0wcoJY5yDL/rV3OmYEwXIzg==} engines: {node: '>=16.20.1'} @@ -11335,24 +10264,12 @@ packages: ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - ms@2.1.1: - resolution: {integrity: sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==} - ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - msgpack-js@0.3.0: - resolution: {integrity: sha512-dBIO+q0IAtZMeTn8K1gr0NuM0OvXEV97NwFsJQKzJ/qkQI9d5MN7Vc++TAUkIxaoIMJyIgMByOAwoJO2wdYDrA==} - - msgpack-stream@0.0.13: - resolution: {integrity: sha512-Wh+t8IJrHPzSjph4wKJhenKG8vvtT0RDebLf1k1RSuRNOJ7caLFvwDnkyiihhZ5QJJmSg0KpjvqtDj9FvvWHWg==} - - msgpack5@4.5.1: - resolution: {integrity: sha512-zC1vkcliryc4JGlL6OfpHumSYUHWFGimSI+OgfRCjTFLmKA2/foR9rMTOhWiqfOrfxJOctrpWPvrppf8XynJxw==} - msgpackr-extract@3.0.2: resolution: {integrity: sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==} hasBin: true @@ -11382,17 +10299,6 @@ packages: resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} engines: {node: ^18.17.0 || >=20.5.0} - mux-demux@3.7.9: - resolution: {integrity: sha512-zf+kqfl+e/U+0MSqJwUg+Wzbyxucf8YK6Sxyzy94gzS6ichxcEV2mUpXD7hPhCTKAVpX6s00ihYbJC/aH8gxwA==} - - nanoid@2.1.11: - resolution: {integrity: sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==} - - nanoid@3.3.4: - resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -11456,10 +10362,6 @@ packages: resolution: {integrity: sha512-WHUWSXRSobqcYD++EGgxXU7asFcECICUSiDfrpzK5c+V2luNVZp2vAas0yje45CC3XPkZb6J0CRIMyEZLQK2sg==} engines: {node: '>=14.0.0'} - nocache@2.1.0: - resolution: {integrity: sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q==} - engines: {node: '>=4.0.0'} - node-abi@3.47.0: resolution: {integrity: sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A==} engines: {node: '>=10'} @@ -11467,9 +10369,6 @@ packages: node-addon-api@4.3.0: resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==} - node-environment-flags@1.0.6: - resolution: {integrity: sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==} - node-eta@0.9.0: resolution: {integrity: sha512-mTCTZk29tmX1OGfVkPt63H3c3VqXrI2Kvua98S7iUIB/Gbp0MNw05YtUomxQIxnnKMyRIIuY9izPcFixzhSBrA==} @@ -11507,30 +10406,10 @@ packages: node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} - nodemailer-direct-transport@3.3.2: - resolution: {integrity: sha512-vEMLWdUZP9NpbeabM8VTiB3Ar1R0ixASp/6DdKX372LK4USKB4Lq12/WCp69k/+kWk4RiCWWEGo57CcsXOs/bw==} - - nodemailer-fetch@1.6.0: - resolution: {integrity: sha512-P7S5CEVGAmDrrpn351aXOLYs1R/7fD5NamfMCHyi6WIkbjS2eeZUB/TkuvpOQr0bvRZicVqo59+8wbhR3yrJbQ==} - - nodemailer-ses-transport@1.5.1: - resolution: {integrity: sha512-JwL93Lc7KEWbH4a9Ehm6XCJgNhf6QNleSDkIsCvEyViKzqvYsf+8rF2PG8OzI1xDyxvtgsaWAmJWMqABOZmnWg==} - - nodemailer-shared@1.1.0: - resolution: {integrity: sha512-68xW5LSyPWv8R0GLm6veAvm7E+XFXkVgvE3FW0FGxNMMZqMkPFeGDVALfR1DPdSfcoO36PnW7q5AAOgFImEZGg==} - - nodemailer-stub-transport@1.1.0: - resolution: {integrity: sha512-4fwl2f+647IIyuNuf6wuEMqK4oEU9FMJSYme8kPckVSr1rXIXcmI6BNcIWO+1cAK8XeexYKxYoFztam0jAwjkA==} - nodemailer@6.9.10: resolution: {integrity: sha512-qtoKfGFhvIFW5kLfrkw2R6Nm6Ur4LNUMykyqu6n9BRKJuyQrqEGwdXXUAbwWEKt33dlWUGXb7rzmJP/p4+O+CA==} engines: {node: '>=6.0.0'} - nodemon@2.0.16: - resolution: {integrity: sha512-zsrcaOfTWRuUzBn3P44RDliLlp263Z/76FPoHFr3cFFkOz0lTPAcIw8dCzfdVIx/t3AtDYCZRCDkoCojJqaG3w==} - engines: {node: '>=8.10.0'} - hasBin: true - nopt@1.0.10: resolution: {integrity: sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==} hasBin: true @@ -11590,12 +10469,6 @@ packages: nwsapi@2.2.7: resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==} - oauth-sign@0.9.0: - resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} - - oauth@0.9.15: - resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==} - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -11651,10 +10524,6 @@ packages: resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} engines: {node: '>= 0.4'} - object.getownpropertydescriptors@2.1.7: - resolution: {integrity: sha512-PrJz0C2xJ58FNn11XV2lr4Jt5Gzl94qpy9Lu0JlfEj14z88sqbSBJCBEzdlNUCzY2gburhbrwOZ5BHCmuNUy0g==} - engines: {node: '>= 0.8'} - object.groupby@1.0.1: resolution: {integrity: sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==} @@ -11726,18 +10595,10 @@ packages: resolution: {integrity: sha512-vz9iS7MJ5+Bp1URw8Khvdyw1H/hGvzHWlKQ7eRrQojSCDL1/SrWfrY9QebLw97n2deyRtzHRC3MkQfVNUCo91Q==} engines: {node: '>=0.10'} - optional-require@1.1.8: - resolution: {integrity: sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==} - engines: {node: '>=4'} - optionator@0.9.3: resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} engines: {node: '>= 0.8.0'} - options@0.0.6: - resolution: {integrity: sha512-bOj3L1ypm++N+n7CEbbe473A414AB7z+amKYshRb//iuL3MpdDCLhPnw6aVTdKB9g5ZRVHIEp8eUln6L2NUStg==} - engines: {node: '>=0.4.0'} - ora@5.4.1: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} @@ -11748,14 +10609,6 @@ packages: os-browserify@0.3.0: resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==} - os-locale@3.1.0: - resolution: {integrity: sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==} - engines: {node: '>=6'} - - os-locale@5.0.0: - resolution: {integrity: sha512-tqZcNEDAIZKBEPnHPlVDvKrp7NzgLi7jRmhKiUoa2NUmhl13FtkAGLUVR+ZsYvApBQdBfYm43A4tXXQ4IrYLBA==} - engines: {node: '>=10'} - os-tmpdir@1.0.2: resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} engines: {node: '>=0.10.0'} @@ -11787,10 +10640,6 @@ packages: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} - p-is-promise@2.1.0: - resolution: {integrity: sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==} - engines: {node: '>=6'} - p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -11871,10 +10720,6 @@ packages: parse-latin@4.3.0: resolution: {integrity: sha512-TYKL+K98dcAWoCw/Ac1yrPviU8Trk+/gmjQVaoWEFDZmVD4KRg6c/80xKqNNFQObo2mTONgF8trzAf2UTwKafw==} - parse-passwd@1.0.0: - resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} - engines: {node: '>=0.10.0'} - parse-path@4.0.4: resolution: {integrity: sha512-Z2lWUis7jlmXC1jeOG9giRO2+FsuyNipeQ43HAjqAZjwSe3SEf+q/84FGPHoso3kyntbxa4c4i77t3m6fGf8cw==} @@ -11910,37 +10755,6 @@ packages: resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==} engines: {node: '>=0.10.0'} - passport-auth0@1.4.2: - resolution: {integrity: sha512-cIPIhN0WbgXWxU0VrKXLT0eF/3jeZ6JJwkypUMpxjH4MOVDIUfU0qBeZBVZySd8WkkIzRNG/EY0lZqKflYJIFA==} - - passport-local@1.0.0: - resolution: {integrity: sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==} - engines: {node: '>= 0.4.0'} - - passport-mock-strategy@2.0.0: - resolution: {integrity: sha512-9YUT0sja/7n+HfQ+Jwx4XETERRh1uciRjpHhEZMcYS1FBnMrfrSlKVS42bMU06ewSFiPhXztazAE6XwiZdZQ/g==} - engines: {node: '>= 6'} - - passport-oauth1@1.3.0: - resolution: {integrity: sha512-8T/nX4gwKTw0PjxP1xfD0QhrydQNakzeOpZ6M5Uqdgz9/a/Ag62RmJxnZQ4LkbdXGrRehQHIAHNAu11rCP46Sw==} - engines: {node: '>= 0.4.0'} - - passport-oauth2@1.7.0: - resolution: {integrity: sha512-j2gf34szdTF2Onw3+76alNnaAExlUmHvkc7cL+cmaS5NzHzDP/BvFHJruueQ9XAeNOdpI+CH+PWid8RA7KCwAQ==} - engines: {node: '>= 0.4.0'} - - passport-oauth@1.0.0: - resolution: {integrity: sha512-4IZNVsZbN1dkBzmEbBqUxDG8oFOIK81jqdksE3HEb/vI3ib3FMjbiZZ6MTtooyYZzmKu0BfovjvT1pdGgIq+4Q==} - engines: {node: '>= 0.4.0'} - - passport-strategy@1.0.0: - resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==} - engines: {node: '>= 0.4.0'} - - passport@0.4.1: - resolution: {integrity: sha512-IxXgZZs8d7uFSt3eqNjM9NQ3g3uQCW5avD8mRNoXV99Yig50vjuaez6dQK2qC0kVWPRTujxY0dWgGfT09adjYg==} - engines: {node: '>= 0.4.0'} - password-prompt@1.1.3: resolution: {integrity: sha512-HkrjG2aJlvF0t2BMH0e2LB/EHf3Lcq3fNMzy4GYHcQblAvOl+QQji1Lx7WRBMqpVK8p+KR7bCg7oqAMXtdgqyw==} @@ -12004,9 +10818,6 @@ packages: pause-stream@0.0.11: resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} - pause@0.0.1: - resolution: {integrity: sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==} - pbkdf2@3.1.2: resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} engines: {node: '>=0.12'} @@ -12504,9 +11315,6 @@ packages: psl@1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} - pstree.remy@1.1.8: - resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} - public-encrypt@4.0.3: resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==} @@ -12517,9 +11325,6 @@ packages: resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==} engines: {node: '>=6'} - punycode@1.3.2: - resolution: {integrity: sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==} - punycode@1.4.1: resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} @@ -12587,11 +11392,6 @@ packages: resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==} engines: {node: '>=0.4.x'} - querystring@0.2.0: - resolution: {integrity: sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==} - engines: {node: '>=0.4.x'} - deprecated: The querystring API is considered Legacy. new code should use the URLSearchParams API instead. - querystring@0.2.1: resolution: {integrity: sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==} engines: {node: '>=0.4.x'} @@ -12623,10 +11423,6 @@ packages: resolution: {integrity: sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==} engines: {node: '>=0.12'} - random-bytes@1.0.0: - resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==} - engines: {node: '>= 0.8'} - randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} @@ -12641,9 +11437,6 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} - rate-limit-mongo@2.3.2: - resolution: {integrity: sha512-dLck0j5N/AX9ycVHn5lX9Ti2Wrrwi1LfbXitu/mMBZOo2nC26RgYKJVbcb2mYgb9VMaPI2IwJVzIa2hAQrMaDA==} - raw-body@2.5.1: resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} engines: {node: '>= 0.8'} @@ -12950,10 +11743,6 @@ packages: redux@4.2.1: resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} - referrer-policy@1.2.0: - resolution: {integrity: sha512-LgQJIuS6nAy1Jd88DCQRemyE3mS+ispwlqMk3b0yjZ257fI1v9c+/p6SD5gP5FGyXUIgrNOAfmyioHwZtYv2VA==} - engines: {node: '>=4.0.0'} - reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -12972,9 +11761,6 @@ packages: regenerate@1.4.2: resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} - regenerator-runtime@0.13.11: - resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} - regenerator-runtime@0.14.0: resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} @@ -13077,15 +11863,6 @@ packages: resolution: {integrity: sha512-vuNYXC7gG7IeVNBC1xUllqCcZKRbJoSPOBhnTEcAIiKCsbuef6zO3F0Rve3isPMMoNoQRWjQwbAgAjHUHniyEA==} engines: {node: '>= 0.10'} - request@2.88.2: - resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} - engines: {node: '>= 6'} - deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 - - require-at@1.0.6: - resolution: {integrity: sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==} - engines: {node: '>=4'} - require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -13200,9 +11977,6 @@ packages: ripemd160@2.0.2: resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} - rndm@1.2.0: - resolution: {integrity: sha512-fJhQQI5tLrQvYIYFpOnFinzv9dwmR7hRnUz1XqP3OJ1jIweTNOd6aTO4jwQSgcBSFUB+/KHJxuGneime+FdzOw==} - rollup@3.29.4: resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} @@ -13226,9 +12000,6 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - rx@4.1.0: - resolution: {integrity: sha512-CiaiuN6gapkdl+cZUr67W6I8jquN4lkak3vtIsIWCl4XIPP8ffsoyN6/+PuGXnQy8Cu8W2y9Xxh31Rq4M6wUug==} - rxjs@6.6.7: resolution: {integrity: sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==} engines: {npm: '>=2.0.0'} @@ -13248,9 +12019,6 @@ packages: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} - safe-buffer@5.1.1: - resolution: {integrity: sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==} - safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} @@ -13287,19 +12055,9 @@ packages: sanitize-html@2.11.0: resolution: {integrity: sha512-BG68EDHRaGKqlsNjJ2xUB7gpInPA8gVx/mvjO743hZaeMCZ2DwzW7xvsqZ+KNU4QKwj86HJ3uu2liISf2qBBUA==} - saslprep@1.0.3: - resolution: {integrity: sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==} - engines: {node: '>=6'} - sass.js@0.11.1: resolution: {integrity: sha512-X9AtzYFr/HZ+pDIxX6xN74w/H9JjnDHqZcsYY8mr/SpCyhDVN1pJ3G0Q9rb+z3pZ7obZdYuTYMbKl1ALuhbZDw==} - sax@1.2.1: - resolution: {integrity: sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==} - - sax@1.3.0: - resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==} - saxes@5.0.1: resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} engines: {node: '>=10'} @@ -13375,10 +12133,6 @@ packages: serialize-javascript@6.0.1: resolution: {integrity: sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==} - serve-favicon@2.5.0: - resolution: {integrity: sha512-FMW2RvqNr03x+C0WxTyu6sOv21oOjkq5j8tjquWccwa6ScNyGFOGJVpuS1NmTVGBAHS07xnSKotgf2ehQmf9iA==} - engines: {node: '>= 0.8.0'} - serve-handler@6.1.3: resolution: {integrity: sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==} @@ -13431,9 +12185,6 @@ packages: setprototypeof@1.1.0: resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} - setprototypeof@1.1.1: - resolution: {integrity: sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==} - setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -13488,10 +12239,6 @@ packages: shimmer@1.2.1: resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} - shortid@2.2.16: - resolution: {integrity: sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g==} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - shx@0.3.4: resolution: {integrity: sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==} engines: {node: '>=6'} @@ -13548,10 +12295,6 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - slash@2.0.0: - resolution: {integrity: sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==} - engines: {node: '>=6'} - slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -13580,9 +12323,6 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - smtp-connection@2.12.0: - resolution: {integrity: sha512-UP5jK4s5SGcUcqPN4U9ingqKt9mXYSKa52YhqxPuMecAnUOsVJpOmtgGaOm1urUBJZlzDt1M9WhZZkgbhxQlvg==} - snapdragon-node@2.1.1: resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==} engines: {node: '>=0.10.0'} @@ -13707,15 +12447,6 @@ packages: sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} - sse@0.0.8: - resolution: {integrity: sha512-cviG7JH31TUhZeaEVhac3zTzA+2FwA7qvHziAHpb7mC7RNVJ/RbHN+6LIGsS2ugP4o2H15DWmrSMK+91CboIcg==} - engines: {node: '>=0.4.0'} - - sshpk@1.17.0: - resolution: {integrity: sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==} - engines: {node: '>=0.10.0'} - hasBin: true - st@2.0.0: resolution: {integrity: sha512-drN+aGYnrZPNYIymmNwIY7LXYJ8MqsqXj4fMRue3FOgGMdGjSX10fhJ3qx0sVQPhcWxhEaN4U/eWM4O4dbYNAw==} hasBin: true @@ -13765,18 +12496,12 @@ packages: stream-combiner2@1.1.1: resolution: {integrity: sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==} - stream-combiner@0.0.2: - resolution: {integrity: sha512-Z2D5hPQapscuHNqiyUgjnF1sxG/9CB7gs1a9vcS2/OvMiFwmm6EZw9IjbU34l5mPXS62RidpoBdyB83E0GXHLw==} - stream-combiner@0.2.2: resolution: {integrity: sha512-6yHMqgLYDzQDcAkL+tjJDC5nSNuNIx0vZtRZeiPh7Saef7VHX9H5Ijn9l2VIol2zaNYlYEX6KyuT/237A58qEQ==} stream-http@3.2.0: resolution: {integrity: sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==} - stream-serializer@1.1.2: - resolution: {integrity: sha512-I/GbDmZwBLn4/gpW4gOwt+jc/cVXt0kQwLOBuY/YLIACfwAnK88qzvSHyyu1+YgoALrWTgbnAVRRirVjGUCTBg==} - stream-splicer@2.0.1: resolution: {integrity: sha512-Xizh4/NPuYSyAXyT7g8IvdJ9HJpxIGL9PjyhtywCZvvP0OPIdqyrr4dMikeuvY8xahpdKEBlBTySe583totajg==} @@ -13939,33 +12664,9 @@ packages: resolution: {integrity: sha512-HNPTzXrSompUxeGqDpbNmPvH/UEzziIxGWvTDTZwIBLlL6CFqR+3YLpV+g4HXOsPKbQRguauptQwVWO7Y/yEGg==} engines: {node: '>=12.*'} - stripe@8.205.0: - resolution: {integrity: sha512-hmYnc7je6j0n9GlkUpc8USsUquLzSxmWj78g9NKFokCtSybNy7y9fYS+VB5AuZUwmIkzhTczgf+TaSmI4kbk9A==} - engines: {node: ^8.1 || >=10.*} - strnum@1.0.5: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} - strong-error-handler@3.5.0: - resolution: {integrity: sha512-PCMOf6RYni7wMD3ytGN/TBIJdKZ/EfgItgE8tVrJNGVAf2X39L7I0r/tlDyn+1G9qfVCZL0mSeutljpkOpBy1Q==} - engines: {node: '>=10'} - - strong-globalize@4.1.3: - resolution: {integrity: sha512-SJegV7w5D4AodEspZJtJ7rls3fmi+Zc0PdyJCqBsg4RN9B8TC80/uAI2fikC+s1Jp9FLvr2vDX8f0Fqc62M4OA==} - engines: {node: '>=6'} - - strong-globalize@5.1.0: - resolution: {integrity: sha512-9cooAb6kNMDFmTDybkkch1x7b+LuzZNva8oIr+MxXnvx9jcvw4/4DTSXPc53mG68G0Q9YOTYZkhDkWe/DiJ1Qg==} - engines: {node: '>=8.9'} - - strong-globalize@6.0.6: - resolution: {integrity: sha512-+mN0wTXBg9rLiKBk7jsyfXFWsg08q160XQcmJ3gNxSQ8wrC668dzR8JUp/wcK3NZ2eQ5h5tvc8O6Y+FC0D61lw==} - engines: {node: '>=10'} - - strong-remoting@3.17.0: - resolution: {integrity: sha512-MfDyLxmoSizuxBE5C8S2A9nPmy4sQquoZNs6NtbSEmaX2OFKlvb/AhTKU9An+Xuee1RRQHEIun8Q/nO+Lp/H6g==} - engines: {node: '>=8'} - strtok3@6.3.0: resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==} engines: {node: '>=10'} @@ -14040,10 +12741,6 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - swagger-ui@2.2.10: - resolution: {integrity: sha512-dXSMq5umiy6XJNhpiYBYOsjMvq3+qoISWL55cMtOeoNqv/gA6NQ19F+4gJWQ81PL4V/j/F6V6tA5aSlCIV3PKg==} - deprecated: No longer maintained, please upgrade to swagger-ui@3. - symbol-observable@1.2.0: resolution: {integrity: sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==} engines: {node: '>=0.10.0'} @@ -14153,9 +12850,6 @@ packages: through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} - through@2.3.4: - resolution: {integrity: sha512-DwbmSAcABsMazNkLOJJSLRC3gfh4cPxUxJCn9npmvbcI6undhgoJ2ShvEOgZrW8BH62Gyr9jKboGbfFcmY5VsQ==} - through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -14214,9 +12908,6 @@ packages: to-space-case@1.0.0: resolution: {integrity: sha512-rLdvwXZ39VOn1IxGL3V6ZstoTbwLRckQmn/U8ZDLuWwIXNpuZDhQ3AiRUlhTbOXFVE9C+dR51wM0CBDhk31VcA==} - to-utf8@0.0.1: - resolution: {integrity: sha512-zks18/TWT1iHO3v0vFp5qLKOG27m67ycq/Y7a7cTiRuUNlc4gf3HGnkRgMv0NyhnfTamtkYBJl+YeD1/j07gBQ==} - to-vfile@5.0.3: resolution: {integrity: sha512-z1Lfx60yAMDMmr+f426Y4yECsHdl8GVEAE+LymjRF5oOIZ7T4N20IxWNAxXLMRzP9jSSll38Z0fKVAhVLsdLOw==} @@ -14239,21 +12930,10 @@ packages: tone@14.7.77: resolution: {integrity: sha512-tCfK73IkLHyzoKUvGq47gyDyxiKLFvKiVCOobynGgBB9Dl0NkxTM2p+eRJXyCYrjJwy9Y0XCMqD3uOYsYt2Fdg==} - toposort@2.0.2: - resolution: {integrity: sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==} - totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} - touch@3.1.0: - resolution: {integrity: sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==} - hasBin: true - - tough-cookie@2.5.0: - resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} - engines: {node: '>=0.8'} - tough-cookie@4.1.3: resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==} engines: {node: '>=6'} @@ -14277,9 +12957,6 @@ packages: resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==} engines: {node: '>=14'} - traverse@0.6.7: - resolution: {integrity: sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==} - trim-trailing-lines@1.1.4: resolution: {integrity: sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ==} @@ -14362,10 +13039,6 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - tsscmp@1.0.6: - resolution: {integrity: sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==} - engines: {node: '>=0.6.x'} - tsutils@3.21.0: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} @@ -14383,12 +13056,6 @@ packages: tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - tweetnacl@0.14.5: - resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} - - twostep@0.4.2: - resolution: {integrity: sha512-O/wdPYk9ey04qcCiw8AQN74DbvLFZLAgnryrNTpV7T/sxB4lcGkCMHynx5xCcA6fCh739ZAqp3HcGhy770X1qA==} - type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -14509,16 +13176,6 @@ packages: uc.micro@2.0.0: resolution: {integrity: sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==} - uid-safe@2.1.5: - resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==} - engines: {node: '>= 0.8'} - - uid2@0.0.3: - resolution: {integrity: sha512-5gSP1liv10Gjp8cMEnFd6shzkL/D6W1uhXSFNCxDC+YI8+L8wkCYCbJ7n77Ezb4wE/xzMogecE+DtamEe9PZjg==} - - uid2@0.0.4: - resolution: {integrity: sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==} - umd@3.0.3: resolution: {integrity: sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==} hasBin: true @@ -14541,21 +13198,9 @@ packages: resolution: {integrity: sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==} hasBin: true - undefsafe@2.0.5: - resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} - underscore.string@3.3.6: resolution: {integrity: sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==} - underscore@1.12.1: - resolution: {integrity: sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==} - - underscore@1.13.6: - resolution: {integrity: sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==} - - underscore@1.7.0: - resolution: {integrity: sha512-cp0oQQyZhUM1kpJDLdGO1jPZHgS/MpzoWYfe9+CM2h/QGDZlqwT2T3YGukuBdaNJ/CAPoeyAZRRHz8JFo176vA==} - undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -14747,9 +13392,6 @@ packages: url-parse@1.5.10: resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - url@0.10.3: - resolution: {integrity: sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==} - url@0.11.3: resolution: {integrity: sha512-6hxOLGfZASQK/cijlZnZJTq8OXAkt/3YGfQX45vvMYXpZoo8NdWZcY73K108Jf759lS1Bv/8wXnHDTSz17dSRw==} @@ -14790,10 +13432,6 @@ packages: resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==} hasBin: true - uuid@8.0.0: - resolution: {integrity: sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==} - hasBin: true - uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true @@ -14817,10 +13455,6 @@ packages: resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==} engines: {node: '>=10.12.0'} - v8flags@3.2.0: - resolution: {integrity: sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==} - engines: {node: '>= 0.10'} - valid-url@1.0.9: resolution: {integrity: sha512-QQDsV8OnSf5Uc30CKSwG9lnhMPe6exHtTXLRYX8uMwKENy640pU+2BgBL0LRbDh/eYRahNCS7aewCx0wf3NYVA==} @@ -14831,10 +13465,6 @@ packages: resolution: {integrity: sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==} engines: {node: '>= 0.10'} - validator@13.7.0: - resolution: {integrity: sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==} - engines: {node: '>= 0.10'} - value-or-promise@1.0.6: resolution: {integrity: sha512-9r0wQsWD8z/BxPOvnwbPf05ZvFngXyouE9EKB+5GbYix+BYnAwrIChCUyFIinfbf2FL/U71z+CPpbnmTdxrwBg==} engines: {node: '>=12'} @@ -14843,10 +13473,6 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - verror@1.10.0: - resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} - engines: {'0': node >=0.6.0} - vfile-location@2.0.6: resolution: {integrity: sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==} @@ -15184,10 +13810,6 @@ packages: x-is-string@0.1.0: resolution: {integrity: sha512-GojqklwG8gpzOVEVki5KudKNoq7MbbjYZCbyWzEz7tyPA7eleiE0+ePwOWQQRb5fm86rD3S8Tc0tSFf3AOv50w==} - x-xss-protection@1.3.0: - resolution: {integrity: sha512-kpyBI9TlVipZO4diReZMAHWtS0MMa/7Kgx8hwG/EuZLiA6sg4Ah/4TRdASHhRRN3boobzcYgFRUFSgHRge6Qhg==} - engines: {node: '>=4.0.0'} - xdg-basedir@4.0.0: resolution: {integrity: sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==} engines: {node: '>=8'} @@ -15199,27 +13821,9 @@ packages: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} engines: {node: '>=12'} - xml2js@0.4.23: - resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==} - engines: {node: '>=4.0.0'} - - xml2js@0.5.0: - resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} - engines: {node: '>=4.0.0'} - - xmlbuilder@11.0.1: - resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} - engines: {node: '>=4.0'} - xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - xmlcreate@1.0.2: - resolution: {integrity: sha512-Mbe56Dvj00onbnSo9J0qj/XlY5bfN9KidsOnpd5tRCsR3ekB3hyyNU9fGrTdqNT5ZNvv4BsA2TcQlignsZyVcw==} - - xmlcreate@2.0.4: - resolution: {integrity: sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==} - xmlhttprequest-ssl@1.6.3: resolution: {integrity: sha512-3XfeQE/wNkvrIktn2Kf0869fC0BN6UpydVasGIeSm2B1Llihf7/0UfZM+eCkOw3P7bP4+qPgqhm7ZoxuJtFU0Q==} engines: {node: '>=0.4.0'} @@ -15232,10 +13836,6 @@ packages: xstate@4.38.2: resolution: {integrity: sha512-Fba/DwEPDLneHT3tbJ9F3zafbQXszOlyCJyQqqdzmtlY/cwE2th462KK48yaANf98jHlP6lJvxfNtN0LFKXPQg==} - xtend@1.0.3: - resolution: {integrity: sha512-wv78b3q8kHDveC/C7Yq/UUrJXsAAM1t/j5m28h/ZlqYy0+eqByglhsWR88D2j3VImQzZlNIDsSbZ3QItwgWEGw==} - engines: {node: '>=0.4'} - xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -15278,10 +13878,6 @@ packages: resolution: {integrity: sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==} engines: {node: '>= 14'} - yamljs@0.3.0: - resolution: {integrity: sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==} - hasBin: true - yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} @@ -16359,20 +14955,6 @@ snapshots: '@smithy/types': 4.2.0 tslib: 2.6.2 - '@babel/cli@7.17.10(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@jridgewell/trace-mapping': 0.3.19 - commander: 4.1.1 - convert-source-map: 1.9.0 - fs-readdir-recursive: 1.1.0 - glob: 7.2.3 - make-dir: 2.1.0 - slash: 2.0.0 - optionalDependencies: - '@nicolo-ribaudo/chokidar-2': 2.1.8-no-fsevents.3 - chokidar: 3.6.0 - '@babel/code-frame@7.10.4': dependencies: '@babel/highlight': 7.24.7 @@ -16402,10 +14984,6 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.22.20': {} - - '@babel/compat-data@7.23.3': {} - '@babel/compat-data@7.23.5': {} '@babel/core@7.10.5': @@ -16429,26 +15007,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/core@7.18.0': - dependencies: - '@ampproject/remapping': 2.2.1 - '@babel/code-frame': 7.22.13 - '@babel/generator': 7.23.0 - '@babel/helper-compilation-targets': 7.22.15 - '@babel/helper-module-transforms': 7.23.0(@babel/core@7.18.0) - '@babel/helpers': 7.23.1 - '@babel/parser': 7.23.0 - '@babel/template': 7.22.15 - '@babel/traverse': 7.23.0 - '@babel/types': 7.23.0 - convert-source-map: 1.9.0 - debug: 4.3.4(supports-color@8.1.1) - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/core@7.23.0': dependencies: '@ampproject/remapping': 2.2.1 @@ -16505,13 +15063,6 @@ snapshots: eslint-visitor-keys: 2.1.0 semver: 6.3.1 - '@babel/generator@7.23.0': - dependencies: - '@babel/types': 7.23.3 - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.19 - jsesc: 2.5.2 - '@babel/generator@7.23.3': dependencies: '@babel/types': 7.23.9 @@ -16553,14 +15104,6 @@ snapshots: dependencies: '@babel/types': 7.23.9 - '@babel/helper-compilation-targets@7.22.15': - dependencies: - '@babel/compat-data': 7.23.3 - '@babel/helper-validator-option': 7.23.5 - browserslist: 4.22.1 - lru-cache: 5.1.1 - semver: 6.3.1 - '@babel/helper-compilation-targets@7.23.6': dependencies: '@babel/compat-data': 7.23.5 @@ -16569,19 +15112,6 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.22.15(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.23.0 - '@babel/helper-member-expression-to-functions': 7.23.0 - '@babel/helper-optimise-call-expression': 7.22.5 - '@babel/helper-replace-supers': 7.22.20(@babel/core@7.18.0) - '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.22.15(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -16608,13 +15138,6 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 semver: 6.3.1 - '@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-annotate-as-pure': 7.22.5 - regexpu-core: 5.3.2 - semver: 6.3.1 - '@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -16629,18 +15152,6 @@ snapshots: regexpu-core: 5.3.2 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.3.3(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-compilation-targets': 7.23.6 - '@babel/helper-plugin-utils': 7.22.5 - debug: 4.3.4(supports-color@8.1.1) - lodash.debounce: 4.0.8 - resolve: 1.22.8 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/helper-define-polyfill-provider@0.4.4(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -16711,15 +15222,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.23.0(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-module-imports': 7.22.15 - '@babel/helper-simple-access': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - '@babel/helper-validator-identifier': 7.24.7 - '@babel/helper-module-transforms@7.23.3(@babel/core@7.10.5)': dependencies: '@babel/core': 7.10.5 @@ -16729,15 +15231,6 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 '@babel/helper-validator-identifier': 7.24.7 - '@babel/helper-module-transforms@7.23.3(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-module-imports': 7.22.15 - '@babel/helper-simple-access': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - '@babel/helper-validator-identifier': 7.24.7 - '@babel/helper-module-transforms@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -16766,13 +15259,6 @@ snapshots: '@babel/helper-plugin-utils@7.26.5': {} - '@babel/helper-remap-async-to-generator@7.22.20(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-wrap-function': 7.22.20 - '@babel/helper-remap-async-to-generator@7.22.20(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -16787,13 +15273,6 @@ snapshots: '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-wrap-function': 7.22.20 - '@babel/helper-replace-supers@7.22.20(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-member-expression-to-functions': 7.23.0 - '@babel/helper-optimise-call-expression': 7.22.5 - '@babel/helper-replace-supers@7.22.20(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -16844,14 +15323,6 @@ snapshots: '@babel/template': 7.25.0 '@babel/types': 7.25.6 - '@babel/helpers@7.23.1': - dependencies: - '@babel/template': 7.22.15 - '@babel/traverse': 7.23.3 - '@babel/types': 7.23.3 - transitivePeerDependencies: - - supports-color - '@babel/helpers@7.23.2': dependencies: '@babel/template': 7.25.0 @@ -16875,16 +15346,6 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/node@7.17.10(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/register': 7.22.15(@babel/core@7.18.0) - commander: 4.1.1 - core-js: 3.33.0 - node-environment-flags: 1.0.6 - regenerator-runtime: 0.13.11 - v8flags: 3.2.0 - '@babel/parser@7.23.0': dependencies: '@babel/types': 7.23.3 @@ -16909,11 +15370,6 @@ snapshots: dependencies: '@babel/types': 7.26.9 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.22.15(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -16924,13 +15380,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.22.15(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/plugin-transform-optional-chaining': 7.23.3(@babel/core@7.18.0) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -16957,75 +15406,24 @@ snapshots: '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.18.0) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.18.0) - - '@babel/plugin-proposal-class-properties@7.17.12(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.18.0) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-proposal-class-properties@7.17.12(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.23.0) '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-proposal-class-static-block@7.21.0(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.18.0) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.18.0) - - '@babel/plugin-proposal-dynamic-import@7.18.6(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.18.0) - '@babel/plugin-proposal-export-default-from@7.23.3(@babel/core@7.23.7)': dependencies: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-export-default-from': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.18.0) - '@babel/plugin-proposal-function-bind@7.23.3(@babel/core@7.23.7)': dependencies: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-function-bind': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-proposal-json-strings@7.18.6(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.18.0) - - '@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.18.0) - - '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.18.0) - '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17038,12 +15436,6 @@ snapshots: '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.18.0) - '@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.23.7)': dependencies: '@babel/core': 7.23.7 @@ -17057,28 +15449,6 @@ snapshots: '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.10.5) '@babel/plugin-transform-parameters': 7.23.3(@babel/core@7.10.5) - '@babel/plugin-proposal-object-rest-spread@7.18.0(@babel/core@7.18.0)': - dependencies: - '@babel/compat-data': 7.22.20 - '@babel/core': 7.18.0 - '@babel/helper-compilation-targets': 7.22.15 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.18.0) - '@babel/plugin-transform-parameters': 7.22.15(@babel/core@7.18.0) - - '@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.18.0) - - '@babel/plugin-proposal-optional-chaining@7.17.12(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.18.0) - '@babel/plugin-proposal-optional-chaining@7.17.12(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17093,12 +15463,6 @@ snapshots: '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.18.0) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17107,25 +15471,6 @@ snapshots: dependencies: '@babel/core': 7.23.7 - '@babel/plugin-proposal-private-property-in-object@7.21.11(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.18.0) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.18.0) - - '@babel/plugin-proposal-unicode-property-regex@7.18.6(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.18.0) - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17141,11 +15486,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17156,11 +15496,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17171,11 +15506,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17191,11 +15521,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17211,11 +15536,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-import-assertions@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-import-assertions@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17246,11 +15566,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17286,11 +15601,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17301,11 +15611,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17316,11 +15621,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17336,11 +15636,6 @@ snapshots: '@babel/core': 7.10.5 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17351,11 +15646,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17366,11 +15656,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17381,11 +15666,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17396,11 +15676,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17428,11 +15703,6 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.7) '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-arrow-functions@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-arrow-functions@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17459,13 +15729,6 @@ snapshots: '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.23.7) '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.7) - '@babel/plugin-transform-async-to-generator@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-module-imports': 7.22.15 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.18.0) - '@babel/plugin-transform-async-to-generator@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17480,11 +15743,6 @@ snapshots: '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.23.7) - '@babel/plugin-transform-block-scoped-functions@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-block-scoped-functions@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17495,11 +15753,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-block-scoping@7.23.0(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-block-scoping@7.23.4(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17536,19 +15789,6 @@ snapshots: '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.23.7) - '@babel/plugin-transform-classes@7.22.15(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-compilation-targets': 7.23.6 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.23.0 - '@babel/helper-optimise-call-expression': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-replace-supers': 7.22.20(@babel/core@7.18.0) - '@babel/helper-split-export-declaration': 7.22.6 - globals: 11.12.0 - '@babel/plugin-transform-classes@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17588,12 +15828,6 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 globals: 11.12.0 - '@babel/plugin-transform-computed-properties@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/template': 7.22.15 - '@babel/plugin-transform-computed-properties@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17606,11 +15840,6 @@ snapshots: '@babel/helper-plugin-utils': 7.22.5 '@babel/template': 7.25.0 - '@babel/plugin-transform-destructuring@7.23.0(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-destructuring@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17621,18 +15850,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-dotall-regex@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.18.0) - '@babel/helper-plugin-utils': 7.22.5 - - '@babel/plugin-transform-dotall-regex@7.23.3(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.18.0) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-dotall-regex@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17645,11 +15862,6 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.7) '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-duplicate-keys@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-duplicate-keys@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17672,12 +15884,6 @@ snapshots: '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-transform-exponentiation-operator@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-builder-binary-assignment-operator-visitor': 7.22.15 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-exponentiation-operator@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17702,11 +15908,6 @@ snapshots: '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-transform-for-of@7.22.15(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-for-of@7.23.6(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17719,13 +15920,6 @@ snapshots: '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/plugin-transform-function-name@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-compilation-targets': 7.23.6 - '@babel/helper-function-name': 7.23.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-function-name@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17752,11 +15946,6 @@ snapshots: '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-transform-literals@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-literals@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17779,11 +15968,6 @@ snapshots: '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.7) - '@babel/plugin-transform-member-expression-literals@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-member-expression-literals@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17794,12 +15978,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-modules-amd@7.23.0(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.18.0) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-modules-amd@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17812,13 +15990,6 @@ snapshots: '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.7) '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-modules-commonjs@7.23.0(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.18.0) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-simple-access': 7.22.5 - '@babel/plugin-transform-modules-commonjs@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17833,14 +16004,6 @@ snapshots: '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-simple-access': 7.22.5 - '@babel/plugin-transform-modules-systemjs@7.23.0(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.18.0) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-validator-identifier': 7.24.7 - '@babel/plugin-transform-modules-systemjs@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17857,12 +16020,6 @@ snapshots: '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-validator-identifier': 7.24.7 - '@babel/plugin-transform-modules-umd@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.18.0) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-modules-umd@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17875,12 +16032,6 @@ snapshots: '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.7) '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.18.0) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17893,11 +16044,6 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.7) '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-new-target@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-new-target@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17950,12 +16096,6 @@ snapshots: '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.7) '@babel/plugin-transform-parameters': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-object-super@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-replace-supers': 7.22.20(@babel/core@7.18.0) - '@babel/plugin-transform-object-super@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -17980,13 +16120,6 @@ snapshots: '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-transform-optional-chaining@7.23.3(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.18.0) - '@babel/plugin-transform-optional-chaining@7.23.4(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -18001,11 +16134,6 @@ snapshots: '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.7) - '@babel/plugin-transform-parameters@7.22.15(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-parameters@7.23.3(@babel/core@7.10.5)': dependencies: '@babel/core': 7.10.5 @@ -18049,11 +16177,6 @@ snapshots: '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.23.7) - '@babel/plugin-transform-property-literals@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-property-literals@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -18157,12 +16280,6 @@ snapshots: '@babel/helper-annotate-as-pure': 7.25.9 '@babel/helper-plugin-utils': 7.26.5 - '@babel/plugin-transform-regenerator@7.22.10(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - regenerator-transform: 0.15.2 - '@babel/plugin-transform-regenerator@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -18175,11 +16292,6 @@ snapshots: '@babel/helper-plugin-utils': 7.22.5 regenerator-transform: 0.15.2 - '@babel/plugin-transform-reserved-words@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-reserved-words@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -18214,11 +16326,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-shorthand-properties@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-shorthand-properties@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -18229,12 +16336,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-spread@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/plugin-transform-spread@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -18247,11 +16348,6 @@ snapshots: '@babel/helper-plugin-utils': 7.22.5 '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/plugin-transform-sticky-regex@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-sticky-regex@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -18262,11 +16358,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-template-literals@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-template-literals@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -18277,11 +16368,6 @@ snapshots: '@babel/core': 7.23.7 '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-typeof-symbol@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-typeof-symbol@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -18300,11 +16386,6 @@ snapshots: '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.7) - '@babel/plugin-transform-unicode-escapes@7.22.10(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-unicode-escapes@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -18327,12 +16408,6 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.7) '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-unicode-regex@7.22.5(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.18.0) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-transform-unicode-regex@7.23.3(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -18357,87 +16432,6 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.7) '@babel/helper-plugin-utils': 7.22.5 - '@babel/preset-env@7.18.0(@babel/core@7.18.0)': - dependencies: - '@babel/compat-data': 7.22.20 - '@babel/core': 7.18.0 - '@babel/helper-compilation-targets': 7.22.15 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-validator-option': 7.22.15 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.22.15(@babel/core@7.18.0) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.22.15(@babel/core@7.18.0) - '@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.18.0) - '@babel/plugin-proposal-class-properties': 7.17.12(@babel/core@7.18.0) - '@babel/plugin-proposal-class-static-block': 7.21.0(@babel/core@7.18.0) - '@babel/plugin-proposal-dynamic-import': 7.18.6(@babel/core@7.18.0) - '@babel/plugin-proposal-export-namespace-from': 7.18.9(@babel/core@7.18.0) - '@babel/plugin-proposal-json-strings': 7.18.6(@babel/core@7.18.0) - '@babel/plugin-proposal-logical-assignment-operators': 7.20.7(@babel/core@7.18.0) - '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.18.0) - '@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.18.0) - '@babel/plugin-proposal-object-rest-spread': 7.18.0(@babel/core@7.18.0) - '@babel/plugin-proposal-optional-catch-binding': 7.18.6(@babel/core@7.18.0) - '@babel/plugin-proposal-optional-chaining': 7.17.12(@babel/core@7.18.0) - '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.18.0) - '@babel/plugin-proposal-private-property-in-object': 7.21.11(@babel/core@7.18.0) - '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.18.0) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.18.0) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.18.0) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.18.0) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.18.0) - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.18.0) - '@babel/plugin-syntax-import-assertions': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.18.0) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.18.0) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.18.0) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.18.0) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.18.0) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.18.0) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.18.0) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.18.0) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.18.0) - '@babel/plugin-transform-arrow-functions': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-async-to-generator': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-block-scoped-functions': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-block-scoping': 7.23.0(@babel/core@7.18.0) - '@babel/plugin-transform-classes': 7.22.15(@babel/core@7.18.0) - '@babel/plugin-transform-computed-properties': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-destructuring': 7.23.0(@babel/core@7.18.0) - '@babel/plugin-transform-dotall-regex': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-duplicate-keys': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-exponentiation-operator': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-for-of': 7.22.15(@babel/core@7.18.0) - '@babel/plugin-transform-function-name': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-literals': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-member-expression-literals': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-modules-amd': 7.23.0(@babel/core@7.18.0) - '@babel/plugin-transform-modules-commonjs': 7.23.0(@babel/core@7.18.0) - '@babel/plugin-transform-modules-systemjs': 7.23.0(@babel/core@7.18.0) - '@babel/plugin-transform-modules-umd': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-new-target': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-object-super': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-parameters': 7.22.15(@babel/core@7.18.0) - '@babel/plugin-transform-property-literals': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-regenerator': 7.22.10(@babel/core@7.18.0) - '@babel/plugin-transform-reserved-words': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-shorthand-properties': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-spread': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-sticky-regex': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-template-literals': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-typeof-symbol': 7.22.5(@babel/core@7.18.0) - '@babel/plugin-transform-unicode-escapes': 7.22.10(@babel/core@7.18.0) - '@babel/plugin-transform-unicode-regex': 7.22.5(@babel/core@7.18.0) - '@babel/preset-modules': 0.1.6(@babel/core@7.18.0) - '@babel/types': 7.23.0 - babel-plugin-polyfill-corejs2: 0.3.3(@babel/core@7.18.0) - babel-plugin-polyfill-corejs3: 0.5.3(@babel/core@7.18.0) - babel-plugin-polyfill-regenerator: 0.3.1(@babel/core@7.18.0) - core-js-compat: 3.33.0 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - '@babel/preset-env@7.23.7(@babel/core@7.23.0)': dependencies: '@babel/compat-data': 7.23.5 @@ -18610,15 +16604,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/preset-modules@0.1.6(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.18.0) - '@babel/plugin-transform-dotall-regex': 7.23.3(@babel/core@7.18.0) - '@babel/types': 7.23.6 - esutils: 2.0.3 - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.23.0)': dependencies: '@babel/core': 7.23.0 @@ -18676,15 +16661,6 @@ snapshots: '@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.23.7) '@babel/plugin-transform-typescript': 7.23.3(@babel/core@7.23.7) - '@babel/register@7.22.15(@babel/core@7.18.0)': - dependencies: - '@babel/core': 7.18.0 - clone-deep: 4.0.1 - find-cache-dir: 2.1.0 - make-dir: 2.1.0 - pirates: 4.0.6 - source-map-support: 0.5.21 - '@babel/register@7.23.7(@babel/core@7.23.7)': dependencies: '@babel/core': 7.23.7 @@ -18739,21 +16715,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/traverse@7.23.3': - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.23.6 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.23.0 - '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.23.9 - '@babel/types': 7.23.9 - debug: 4.3.4(supports-color@8.1.1) - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - '@babel/traverse@7.23.7': dependencies: '@babel/code-frame': 7.24.7 @@ -18820,12 +16781,6 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 - '@babel/types@7.23.6': - dependencies: - '@babel/helper-string-parser': 7.23.4 - '@babel/helper-validator-identifier': 7.24.7 - to-fast-properties: 2.0.0 - '@babel/types@7.23.9': dependencies: '@babel/helper-string-parser': 7.23.4 @@ -19259,15 +17214,6 @@ snapshots: '@freecodecamp/loop-protect@3.0.0': {} - '@freecodecamp/loopback-component-passport@1.2.0': - dependencies: - passport: 0.4.1 - strong-globalize: 4.1.3 - underscore: 1.13.6 - uuid: 3.4.0 - transitivePeerDependencies: - - supports-color - '@freecodecamp/ui@4.0.1(@types/react-dom@17.0.19)(@types/react@17.0.83)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': dependencies: '@fortawesome/fontawesome-svg-core': 6.7.2 @@ -19863,9 +17809,6 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 - '@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3': - optional: true - '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': dependencies: eslint-scope: 5.1.1 @@ -20383,26 +18326,8 @@ snapshots: '@rtsao/scc@1.1.0': {} - '@sentry/core@7.37.1': - dependencies: - '@sentry/types': 7.37.1 - '@sentry/utils': 7.37.1 - tslib: 1.14.1 - '@sentry/core@9.1.0': {} - '@sentry/node@7.37.1': - dependencies: - '@sentry/core': 7.37.1 - '@sentry/types': 7.37.1 - '@sentry/utils': 7.37.1 - cookie: 0.4.2 - https-proxy-agent: 5.0.1 - lru_map: 0.3.3 - tslib: 1.14.1 - transitivePeerDependencies: - - supports-color - '@sentry/node@9.1.0': dependencies: '@opentelemetry/api': 1.9.0 @@ -20452,24 +18377,6 @@ snapshots: '@opentelemetry/semantic-conventions': 1.30.0 '@sentry/core': 9.1.0 - '@sentry/tracing@7.37.1': - dependencies: - '@sentry/core': 7.37.1 - '@sentry/types': 7.37.1 - '@sentry/utils': 7.37.1 - tslib: 1.14.1 - - '@sentry/types@7.37.1': {} - - '@sentry/utils@7.37.1': - dependencies: - '@sentry/types': 7.37.1 - tslib: 1.14.1 - - '@sideway/address@4.1.4': - dependencies: - '@hapi/hoek': 9.3.0 - '@sideway/address@4.1.5': dependencies: '@hapi/hoek': 9.3.0 @@ -21341,13 +19248,6 @@ snapshots: '@types/range-parser': 1.2.5 '@types/send': 0.17.2 - '@types/express@4.17.18': - dependencies: - '@types/body-parser': 1.19.3 - '@types/express-serve-static-core': 4.17.37 - '@types/qs': 6.9.8 - '@types/serve-static': 1.15.3 - '@types/express@4.17.21': dependencies: '@types/body-parser': 1.19.3 @@ -21496,8 +19396,6 @@ snapshots: dependencies: '@types/node': 20.8.0 - '@types/node@10.17.60': {} - '@types/node@14.18.63': {} '@types/node@20.11.20': @@ -21524,10 +19422,6 @@ snapshots: '@types/parse5@5.0.3': {} - '@types/passport@1.0.13': - dependencies: - '@types/express': 5.0.1 - '@types/pg-pool@2.0.6': dependencies: '@types/pg': 8.6.1 @@ -22138,11 +20032,6 @@ snapshots: abstract-logging@2.0.1: {} - accept-language@3.0.18: - dependencies: - bcp47: 1.1.2 - stable: 0.1.8 - accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -22459,14 +20348,6 @@ snapshots: es-abstract: 1.23.9 es-shim-unscopables: 1.1.0 - array.prototype.reduce@1.0.6: - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.22.2 - es-array-method-boxes-properly: 1.0.0 - is-string: 1.0.7 - array.prototype.tosorted@1.1.2: dependencies: call-bind: 1.0.7 @@ -22514,12 +20395,6 @@ snapshots: minimalistic-assert: 1.0.1 safer-buffer: 2.1.2 - asn1@0.2.6: - dependencies: - safer-buffer: 2.1.2 - - assert-plus@1.0.0: {} - assert@1.5.1: dependencies: object.assign: 4.1.7 @@ -22552,16 +20427,8 @@ snapshots: async-retry-ng@2.0.1: {} - async@0.9.2: {} - async@1.5.2: {} - async@2.6.4: - dependencies: - lodash: 4.17.21 - - async@3.2.4: {} - asynciterator.prototype@1.0.0: dependencies: has-symbols: 1.0.3 @@ -22602,23 +20469,6 @@ snapshots: transitivePeerDependencies: - supports-color - aws-sdk@2.1467.0: - dependencies: - buffer: 4.9.2 - events: 1.1.1 - ieee754: 1.1.13 - jmespath: 0.16.0 - querystring: 0.2.0 - sax: 1.2.1 - url: 0.10.3 - util: 0.12.5 - uuid: 8.0.0 - xml2js: 0.5.0 - - aws-sign2@0.7.0: {} - - aws4@1.12.0: {} - axe-core@4.10.3: {} axe-core@4.8.2: {} @@ -22635,12 +20485,6 @@ snapshots: transitivePeerDependencies: - debug - axios@0.22.0(debug@2.2.0): - dependencies: - follow-redirects: 1.15.3(debug@2.2.0) - transitivePeerDependencies: - - debug - axobject-query@3.2.1: dependencies: dequal: 2.0.3 @@ -22735,15 +20579,6 @@ snapshots: cosmiconfig: 7.1.0 resolve: 1.22.8 - babel-plugin-polyfill-corejs2@0.3.3(@babel/core@7.18.0): - dependencies: - '@babel/compat-data': 7.23.5 - '@babel/core': 7.18.0 - '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.18.0) - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - babel-plugin-polyfill-corejs2@0.4.7(@babel/core@7.23.0): dependencies: '@babel/compat-data': 7.23.5 @@ -22780,14 +20615,6 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.5.3(@babel/core@7.18.0): - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.18.0) - core-js-compat: 3.36.0 - transitivePeerDependencies: - - supports-color - babel-plugin-polyfill-corejs3@0.8.7(@babel/core@7.23.0): dependencies: '@babel/core': 7.23.0 @@ -22804,13 +20631,6 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.3.1(@babel/core@7.18.0): - dependencies: - '@babel/core': 7.18.0 - '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.18.0) - transitivePeerDependencies: - - supports-color - babel-plugin-polyfill-regenerator@0.5.5(@babel/core@7.23.0): dependencies: '@babel/core': 7.23.0 @@ -22937,16 +20757,10 @@ snapshots: base64-arraybuffer@0.1.4: {} - base64-js@0.0.2: {} - - base64-js@1.0.2: {} - base64-js@1.5.1: {} base64id@2.0.0: {} - base64url@3.0.1: {} - base@0.11.2: dependencies: cache-base: 1.0.1 @@ -22965,12 +20779,6 @@ snapshots: batch@0.6.1: {} - bcp47@1.1.2: {} - - bcrypt-pbkdf@1.0.2: - dependencies: - tweetnacl: 0.14.5 - bcryptjs@2.4.3: {} better-opn@2.1.1: @@ -22993,11 +20801,6 @@ snapshots: binary-extensions@2.2.0: {} - bl@2.2.1: - dependencies: - readable-stream: 2.3.8 - safe-buffer: 5.2.1 - bl@4.1.0: dependencies: buffer: 5.7.1 @@ -23060,20 +20863,8 @@ snapshots: boolbase@1.0.0: {} - bops@0.0.7: - dependencies: - base64-js: 0.0.2 - to-utf8: 0.0.1 - - bops@1.0.0: - dependencies: - base64-js: 1.0.2 - to-utf8: 0.0.1 - bowser@2.11.0: {} - bowser@2.9.0: {} - boxen@4.2.0: dependencies: ansi-align: 3.0.1 @@ -23285,8 +21076,6 @@ snapshots: bson-objectid@2.0.4: {} - bson@1.1.6: {} - bson@6.9.0: {} buffer-crc32@0.2.13: {} @@ -23297,12 +21086,6 @@ snapshots: buffer-xor@1.0.3: {} - buffer@4.9.2: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - isarray: 1.0.0 - buffer@5.2.1: dependencies: base64-js: 1.5.1 @@ -23418,8 +21201,6 @@ snapshots: camelcase@6.3.0: {} - camelize@1.0.0: {} - caniuse-api@3.0.0: dependencies: browserslist: 4.23.0 @@ -23431,12 +21212,8 @@ snapshots: caniuse-lite@1.0.30001589: {} - canonical-json@0.0.4: {} - canvas-confetti@1.6.0: {} - caseless@0.12.0: {} - ccount@1.1.0: {} ccount@2.0.1: {} @@ -23493,8 +21270,6 @@ snapshots: chardet@0.7.0: {} - charenc@0.0.2: {} - check-error@1.0.3: dependencies: get-func-name: 2.0.2 @@ -23573,8 +21348,6 @@ snapshots: classnames@2.3.2: {} - cldrjs@0.5.5: {} - clean-stack@2.2.0: {} cli-boxes@2.2.1: {} @@ -23697,8 +21470,6 @@ snapshots: commander@2.20.3: {} - commander@4.1.1: {} - commander@7.2.0: {} commander@9.5.0: {} @@ -23759,20 +21530,6 @@ snapshots: confusing-browser-globals@1.0.11: {} - connect-flash@0.1.1: {} - - connect-mongo@3.2.0(express-session@1.17.3): - dependencies: - express-session: 1.17.3 - mongodb: 3.6.9 - transitivePeerDependencies: - - aws4 - - bson-ext - - kerberos - - mongodb-client-encryption - - mongodb-extjson - - snappy - connect@3.7.0: dependencies: debug: 2.6.9 @@ -23796,8 +21553,6 @@ snapshots: dependencies: safe-buffer: 5.2.1 - content-security-policy-builder@2.1.0: {} - content-type@1.0.5: {} contentful-management@7.54.2(debug@4.3.4): @@ -23827,19 +21582,10 @@ snapshots: convert-source-map@2.0.0: {} - cookie-parser@1.4.6: - dependencies: - cookie: 0.4.1 - cookie-signature: 1.0.6 - cookie-signature@1.0.6: {} cookie-signature@1.2.1: {} - cookie@0.4.0: {} - - cookie@0.4.1: {} - cookie@0.4.2: {} cookie@0.5.0: {} @@ -23862,10 +21608,6 @@ snapshots: serialize-javascript: 6.0.1 webpack: 5.90.3(webpack-cli@4.10.0) - core-js-compat@3.33.0: - dependencies: - browserslist: 4.22.2 - core-js-compat@3.36.0: dependencies: browserslist: 4.23.0 @@ -23879,8 +21621,6 @@ snapshots: core-js@3.33.0: {} - core-util-is@1.0.2: {} - core-util-is@1.0.3: {} cors@2.8.5: @@ -24016,8 +21756,6 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - crypt@0.0.2: {} - crypto-browserify@3.12.0: dependencies: browserify-cipher: 1.0.1 @@ -24034,12 +21772,6 @@ snapshots: crypto-random-string@2.0.0: {} - csrf@3.1.0: - dependencies: - rndm: 1.2.0 - tsscmp: 1.0.6 - uid-safe: 2.1.5 - css-declaration-sorter@6.4.1(postcss@8.4.35): dependencies: postcss: 8.4.35 @@ -24169,13 +21901,6 @@ snapshots: csstype@3.1.3: {} - csurf@1.11.0: - dependencies: - cookie: 0.4.0 - cookie-signature: 1.0.6 - csrf: 3.1.0 - http-errors: 1.7.3 - d@1.0.1: dependencies: es5-ext: 0.10.62 @@ -24185,12 +21910,6 @@ snapshots: dash-ast@1.0.0: {} - dashdash@1.14.1: - dependencies: - assert-plus: 1.0.0 - - dasherize@2.0.0: {} - data-uri-to-buffer@6.0.2: {} data-urls@2.0.0: @@ -24225,8 +21944,6 @@ snapshots: dataloader@2.0.0: {} - date-fns@1.30.1: {} - date-fns@2.30.0: dependencies: '@babel/runtime': 7.23.1 @@ -24243,15 +21960,9 @@ snapshots: dependencies: ms: 2.0.0 - debug@3.1.0: - dependencies: - ms: 2.0.0 - - debug@3.2.7(supports-color@5.5.0): + debug@3.2.7: dependencies: ms: 2.1.3 - optionalDependencies: - supports-color: 5.5.0 debug@4.3.4(supports-color@8.1.1): dependencies: @@ -24291,8 +22002,6 @@ snapshots: dependencies: mimic-response: 3.1.0 - dedent@0.7.0: {} - dedent@1.5.1(babel-plugin-macros@3.1.0): optionalDependencies: babel-plugin-macros: 3.1.0 @@ -24398,8 +22107,6 @@ snapshots: delayed-stream@1.0.0: {} - denque@1.5.1: {} - depd@1.1.2: {} depd@2.0.0: {} @@ -24464,7 +22171,7 @@ snapshots: '@types/tmp': 0.0.33 application-config-path: 0.1.1 command-exists: 1.2.9 - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7 eol: 0.9.1 get-port: 3.2.0 glob: 7.2.3 @@ -24590,8 +22297,6 @@ snapshots: domelementtype: 2.3.0 domhandler: 5.0.3 - dont-sniff-mimetype@1.1.0: {} - dot-prop@5.3.0: dependencies: is-obj: 2.0.0 @@ -24607,8 +22312,6 @@ snapshots: dotenv@16.4.5: {} - dotenv@6.2.0: {} - dotenv@8.6.0: {} dunder-proto@1.0.1: @@ -24617,37 +22320,22 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - duplex@1.0.0: {} - duplexer2@0.1.4: dependencies: readable-stream: 2.3.8 duplexer3@0.1.5: {} - duplexer@0.0.4: {} - duplexer@0.1.2: {} eastasianwidth@0.2.0: {} - ecc-jsbn@0.1.2: - dependencies: - jsbn: 0.1.1 - safer-buffer: 2.1.2 - ecdsa-sig-formatter@1.0.11: dependencies: safe-buffer: 5.2.1 ee-first@1.1.1: {} - ejs@2.7.4: {} - - ejs@3.1.9: - dependencies: - jake: 10.8.7 - electron-to-chromium@1.4.622: {} electron-to-chromium@1.4.685: {} @@ -25009,12 +22697,6 @@ snapshots: es6-object-assign@1.1.0: {} - es6-promise@4.2.8: {} - - es6-promisify@5.0.0: - dependencies: - es6-promise: 4.2.8 - es6-symbol@3.1.3: dependencies: d: 1.0.1 @@ -25123,7 +22805,7 @@ snapshots: eslint-import-resolver-node@0.3.9: dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7 is-core-module: 2.13.1 resolve: 1.22.8 transitivePeerDependencies: @@ -25149,7 +22831,7 @@ snapshots: eslint-module-utils@2.12.0(@typescript-eslint/parser@8.23.0(eslint@9.19.0)(typescript@5.7.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5)(eslint@9.19.0): dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.23.0(eslint@9.19.0)(typescript@5.7.3) eslint: 9.19.0 @@ -25160,7 +22842,7 @@ snapshots: eslint-module-utils@2.8.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.5.5)(eslint@7.32.0): dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 4.33.0(eslint@7.32.0)(typescript@5.2.2) eslint: 7.32.0 @@ -25171,7 +22853,7 @@ snapshots: eslint-module-utils@2.8.0(@typescript-eslint/parser@8.23.0(eslint@9.19.0)(typescript@5.7.3))(eslint-import-resolver-typescript@3.5.5)(eslint@9.19.0): dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.23.0(eslint@9.19.0)(typescript@5.7.3) eslint: 9.19.0 @@ -25210,7 +22892,7 @@ snapshots: array.prototype.findlastindex: 1.2.3 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7 doctrine: 2.1.0 eslint: 7.32.0 eslint-import-resolver-node: 0.3.9 @@ -25238,7 +22920,7 @@ snapshots: array.prototype.findlastindex: 1.2.5 array.prototype.flat: 1.3.2 array.prototype.flatmap: 1.3.2 - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7 doctrine: 2.1.0 eslint: 9.19.0 eslint-import-resolver-node: 0.3.9 @@ -25570,14 +23252,10 @@ snapshots: event-target-shim@5.0.1: {} - eventemitter2@5.0.1: {} - eventemitter3@3.1.2: {} eventemitter3@4.0.7: {} - events@1.1.1: {} - events@3.3.0: {} evp_bytestokey@1.0.3: @@ -25595,18 +23273,6 @@ snapshots: signal-exit: 3.0.7 strip-eof: 1.0.0 - execa@4.1.0: - dependencies: - cross-spawn: 7.0.3 - get-stream: 5.2.0 - human-signals: 1.1.1 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - execa@5.1.1: dependencies: cross-spawn: 7.0.3 @@ -25667,10 +23333,6 @@ snapshots: jest-message-util: 29.7.0 jest-util: 29.7.0 - express-flash@0.0.2: - dependencies: - connect-flash: 0.1.1 - express-graphql@0.12.0(graphql@15.8.0): dependencies: accepts: 1.3.8 @@ -25679,28 +23341,6 @@ snapshots: http-errors: 1.8.0 raw-body: 2.5.2 - express-rate-limit@6.7.0(express@5.0.1): - dependencies: - express: 5.0.1 - - express-session@1.17.3: - dependencies: - cookie: 0.4.2 - cookie-signature: 1.0.6 - debug: 2.6.9 - depd: 2.0.0 - on-headers: 1.0.2 - parseurl: 1.3.3 - safe-buffer: 5.2.1 - uid-safe: 2.1.5 - transitivePeerDependencies: - - supports-color - - express-validator@6.14.1: - dependencies: - lodash: 4.17.21 - validator: 13.11.0 - express@4.18.2: dependencies: accepts: 1.3.8 @@ -25820,10 +23460,6 @@ snapshots: transitivePeerDependencies: - supports-color - extsprintf@1.3.0: {} - - eyes@0.1.8: {} - fast-content-type-parse@1.1.0: {} fast-copy@2.1.7: {} @@ -25946,8 +23582,6 @@ snapshots: fd@0.0.3: {} - feature-policy@0.3.0: {} - figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 @@ -25976,10 +23610,6 @@ snapshots: strtok3: 6.3.0 token-types: 4.2.1 - filelist@1.0.4: - dependencies: - minimatch: 5.1.6 - filesize@6.1.0: {} fill-range@4.0.0: @@ -26089,13 +23719,9 @@ snapshots: flatted@3.3.3: {} - follow-redirects@1.15.3(debug@2.2.0): - optionalDependencies: - debug: 2.2.0 - follow-redirects@1.15.3(debug@3.2.7): optionalDependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7 follow-redirects@1.15.3(debug@4.3.4): optionalDependencies: @@ -26111,8 +23737,6 @@ snapshots: for-in@1.0.2: {} - forever-agent@0.6.1: {} - fork-ts-checker-webpack-plugin@4.1.6(eslint@7.32.0)(typescript@5.2.2)(webpack@5.90.3): dependencies: '@babel/code-frame': 7.24.7 @@ -26129,12 +23753,6 @@ snapshots: transitivePeerDependencies: - supports-color - form-data@2.3.3: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - form-data@3.0.1: dependencies: asynckit: 0.4.0 @@ -26190,8 +23808,6 @@ snapshots: fs-monkey@1.0.5: {} - fs-readdir-recursive@1.1.0: {} - fs.realpath@1.0.0: {} fsevents@2.3.2: @@ -26610,7 +24226,7 @@ snapshots: css-minimizer-webpack-plugin: 2.0.0(webpack@5.90.3) css.escape: 1.5.1 date-fns: 2.30.0 - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7 deepmerge: 4.3.1 del: 5.1.0 detect-port: 1.5.1 @@ -26827,10 +24443,6 @@ snapshots: get-value@2.0.6: {} - getpass@0.1.7: - dependencies: - assert-plus: 1.0.0 - git-up@4.0.5: dependencies: is-ssh: 1.4.0 @@ -26881,10 +24493,6 @@ snapshots: kind-of: 6.0.3 which: 1.3.1 - globalize@1.7.0: - dependencies: - cldrjs: 0.5.5 - globals@11.12.0: {} globals@13.22.0: @@ -27061,13 +24669,6 @@ snapshots: dependencies: duplexer: 0.1.2 - har-schema@2.0.0: {} - - har-validator@5.1.5: - dependencies: - ajv: 6.12.6 - har-schema: 2.0.0 - harmony-reflect@1.6.2: {} has-bigints@1.0.2: {} @@ -27229,29 +24830,6 @@ snapshots: headers-polyfill@4.0.3: {} - helmet-crossdomain@0.4.0: {} - - helmet-csp@2.10.0: - dependencies: - bowser: 2.9.0 - camelize: 1.0.0 - content-security-policy-builder: 2.1.0 - dasherize: 2.0.0 - - helmet@3.23.3: - dependencies: - depd: 2.0.0 - dont-sniff-mimetype: 1.1.0 - feature-policy: 0.3.0 - helmet-crossdomain: 0.4.0 - helmet-csp: 2.10.0 - hide-powered-by: 1.1.0 - hpkp: 2.0.0 - hsts: 2.2.0 - nocache: 2.1.0 - referrer-policy: 1.2.0 - x-xss-protection: 1.3.0 - help-me@4.2.0: dependencies: glob: 8.1.0 @@ -27264,8 +24842,6 @@ snapshots: highlight.js: 10.7.3 minimist: 1.2.8 - hide-powered-by@1.1.0: {} - highlight.js@10.7.3: {} hmac-drbg@1.0.1: @@ -27283,10 +24859,6 @@ snapshots: dependencies: react-is: 16.13.1 - homedir-polyfill@1.0.3: - dependencies: - parse-passwd: 1.0.0 - hookified@1.7.1: {} hosted-git-info@2.8.9: {} @@ -27295,12 +24867,6 @@ snapshots: dependencies: lru-cache: 6.0.0 - hpkp@2.0.0: {} - - hsts@2.2.0: - dependencies: - depd: 2.0.0 - htm@3.1.1: {} html-element-map@1.3.1: @@ -27371,14 +24937,6 @@ snapshots: setprototypeof: 1.1.0 statuses: 1.5.0 - http-errors@1.7.3: - dependencies: - depd: 1.1.2 - inherits: 2.0.4 - setprototypeof: 1.1.1 - statuses: 1.5.0 - toidentifier: 1.0.0 - http-errors@1.8.0: dependencies: depd: 1.1.2 @@ -27428,26 +24986,11 @@ snapshots: transitivePeerDependencies: - debug - http-signature@1.2.0: - dependencies: - assert-plus: 1.0.0 - jsprim: 1.4.2 - sshpk: 1.17.0 - - http-status@1.7.0: {} - http2-wrapper@1.0.3: dependencies: quick-lru: 5.1.1 resolve-alpn: 1.2.1 - httpntlm@1.6.1: - dependencies: - httpreq: 1.1.1 - underscore: 1.7.0 - - httpreq@1.1.1: {} - https-browserify@1.0.0: {} https-proxy-agent@5.0.1: @@ -27464,8 +25007,6 @@ snapshots: transitivePeerDependencies: - supports-color - human-signals@1.1.1: {} - human-signals@2.1.0: {} human-signals@3.0.1: {} @@ -27498,12 +25039,8 @@ snapshots: dependencies: harmony-reflect: 1.6.2 - ieee754@1.1.13: {} - ieee754@1.2.1: {} - ignore-by-default@1.0.1: {} - ignore@4.0.6: {} ignore@5.2.4: {} @@ -27545,8 +25082,6 @@ snapshots: indent-string@4.0.0: {} - inflection@1.13.4: {} - inflight@1.0.6: dependencies: once: 1.4.0 @@ -27653,10 +25188,6 @@ snapshots: dependencies: loose-envify: 1.4.0 - invert-kv@2.0.0: {} - - invert-kv@3.0.1: {} - ip-address@9.0.5: dependencies: jsbn: 1.1.0 @@ -28048,10 +25579,6 @@ snapshots: isarray@2.0.5: {} - isemail@3.2.0: - dependencies: - punycode: 2.3.0 - isexe@2.0.0: {} isobject@2.1.0: @@ -28064,8 +25591,6 @@ snapshots: dependencies: ws: 7.4.5 - isstream@0.1.2: {} - istanbul-lib-coverage@3.2.0: {} istanbul-lib-instrument@5.2.1: @@ -28126,24 +25651,6 @@ snapshots: has-symbols: 1.1.0 set-function-name: 2.0.2 - jake@10.8.7: - dependencies: - async: 3.2.4 - chalk: 4.1.2 - filelist: 1.0.4 - minimatch: 3.1.2 - - jayson@2.1.2: - dependencies: - '@types/node': 10.17.60 - JSONStream: 1.3.5 - commander: 2.20.3 - es6-promisify: 5.0.0 - eyes: 0.1.8 - json-stringify-safe: 5.0.1 - lodash: 4.17.21 - uuid: 3.4.0 - jest-changed-files@29.7.0: dependencies: execa: 5.1.1 @@ -28577,8 +26084,6 @@ snapshots: - supports-color - ts-node - jmespath@0.16.0: {} - joi-objectid@3.0.1: {} joi@17.12.2: @@ -28589,14 +26094,6 @@ snapshots: '@sideway/formula': 3.0.1 '@sideway/pinpoint': 2.0.0 - joi@17.9.2: - dependencies: - '@hapi/hoek': 9.3.0 - '@hapi/topo': 5.1.0 - '@sideway/address': 4.1.4 - '@sideway/formula': 3.0.1 - '@sideway/pinpoint': 2.0.0 - joycon@3.1.1: {} jquery@3.7.1: {} @@ -28616,16 +26113,6 @@ snapshots: dependencies: argparse: 2.0.1 - js2xmlparser@3.0.0: - dependencies: - xmlcreate: 1.0.2 - - js2xmlparser@4.0.2: - dependencies: - xmlcreate: 2.0.4 - - jsbn@0.1.1: {} - jsbn@1.1.0: {} jsdoc-type-pratt-parser@4.0.0: {} @@ -28703,8 +26190,6 @@ snapshots: jsesc@3.1.0: {} - json-buffer@2.0.11: {} - json-buffer@3.0.0: {} json-buffer@3.0.1: {} @@ -28725,12 +26210,8 @@ snapshots: json-schema-traverse@1.0.0: {} - json-schema@0.4.0: {} - json-stable-stringify-without-jsonify@1.0.1: {} - json-stringify-safe@5.0.1: {} - json5@1.0.2: dependencies: minimist: 1.2.8 @@ -28745,19 +26226,6 @@ snapshots: jsonparse@1.3.1: {} - jsonwebtoken@8.5.1: - dependencies: - jws: 3.2.2 - lodash.includes: 4.3.0 - lodash.isboolean: 3.0.3 - lodash.isinteger: 4.0.4 - lodash.isnumber: 3.0.3 - lodash.isplainobject: 4.0.6 - lodash.isstring: 4.0.1 - lodash.once: 4.1.1 - ms: 2.1.3 - semver: 5.7.2 - jsonwebtoken@9.0.2: dependencies: jws: 3.2.2 @@ -28771,13 +26239,6 @@ snapshots: ms: 2.1.3 semver: 7.5.4 - jsprim@1.4.2: - dependencies: - assert-plus: 1.0.0 - extsprintf: 1.3.0 - json-schema: 0.4.0 - verror: 1.10.0 - jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.7 @@ -28851,14 +26312,6 @@ snapshots: dependencies: package-json: 6.5.0 - lcid@2.0.0: - dependencies: - invert-kv: 2.0.0 - - lcid@3.1.1: - dependencies: - invert-kv: 3.0.1 - leven@3.1.0: {} levn@0.4.1: @@ -29044,147 +26497,6 @@ snapshots: longest-streak@3.1.0: {} - loopback-boot@2.28.0: - dependencies: - async: 0.9.2 - commondir: 1.0.1 - debug: 3.2.7(supports-color@5.5.0) - lodash: 4.17.21 - semver: 5.7.2 - strong-globalize: 4.1.3 - toposort: 2.0.2 - transitivePeerDependencies: - - supports-color - - loopback-component-explorer@6.4.0: - dependencies: - debug: 3.2.7(supports-color@5.5.0) - lodash: 4.17.21 - loopback-swagger: 5.9.0 - strong-globalize: 4.1.3 - swagger-ui: 2.2.10 - transitivePeerDependencies: - - supports-color - - loopback-connector-mongodb@5.6.0: - dependencies: - async: 3.2.4 - bson: 1.1.6 - debug: 4.3.4(supports-color@8.1.1) - loopback-connector: 5.3.3 - mongodb: 3.6.9 - strong-globalize: 6.0.6 - transitivePeerDependencies: - - aws4 - - bson-ext - - kerberos - - mongodb-client-encryption - - mongodb-extjson - - snappy - - supports-color - - loopback-connector-remote@3.4.1: - dependencies: - loopback-datasource-juggler: 3.36.1 - strong-remoting: 3.17.0 - transitivePeerDependencies: - - supports-color - - loopback-connector@4.11.1: - dependencies: - async: 3.2.4 - bluebird: 3.7.2 - debug: 4.3.4(supports-color@8.1.1) - msgpack5: 4.5.1 - strong-globalize: 5.1.0 - uuid: 7.0.3 - transitivePeerDependencies: - - supports-color - - loopback-connector@5.3.3: - dependencies: - async: 3.2.4 - bluebird: 3.7.2 - debug: 4.3.4(supports-color@8.1.1) - msgpack5: 4.5.1 - strong-globalize: 6.0.6 - uuid: 9.0.1 - transitivePeerDependencies: - - supports-color - - loopback-datasource-juggler@3.36.1: - dependencies: - async: 2.6.4 - bluebird: 3.7.2 - debug: 3.2.7(supports-color@5.5.0) - depd: 1.1.2 - inflection: 1.13.4 - lodash: 4.17.21 - loopback-connector: 4.11.1 - minimatch: 3.1.2 - qs: 6.11.2 - shortid: 2.2.16 - strong-globalize: 4.1.3 - traverse: 0.6.7 - uuid: 3.4.0 - transitivePeerDependencies: - - supports-color - - loopback-datatype-geopoint@1.0.0: {} - - loopback-filters@1.1.1: - dependencies: - debug: 3.2.7(supports-color@5.5.0) - transitivePeerDependencies: - - supports-color - - loopback-phase@3.4.0: - dependencies: - async: 2.6.4 - debug: 3.2.7(supports-color@5.5.0) - strong-globalize: 4.1.3 - transitivePeerDependencies: - - supports-color - - loopback-swagger@5.9.0: - dependencies: - async: 2.6.4 - debug: 3.2.7(supports-color@5.5.0) - ejs: 2.7.4 - lodash: 4.17.21 - strong-globalize: 4.1.3 - transitivePeerDependencies: - - supports-color - - loopback@3.28.0: - dependencies: - async: 2.6.4 - bcryptjs: 2.4.3 - bluebird: 3.7.2 - body-parser: 1.20.0 - canonical-json: 0.0.4 - debug: 2.2.0 - depd: 1.1.2 - ejs: 2.7.4 - express: 4.18.2 - inflection: 1.13.4 - isemail: 3.2.0 - loopback-connector-remote: 3.4.1 - loopback-datasource-juggler: 3.36.1 - loopback-filters: 1.1.1 - loopback-phase: 3.4.0 - nodemailer: 6.9.10 - nodemailer-direct-transport: 3.3.2 - nodemailer-stub-transport: 1.1.0 - serve-favicon: 2.5.0 - stable: 0.1.8 - strong-globalize: 4.1.3 - strong-remoting: 3.17.0 - uid2: 0.0.3 - underscore.string: 3.3.6 - transitivePeerDependencies: - - supports-color - loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -29225,8 +26537,6 @@ snapshots: dependencies: es5-ext: 0.10.62 - lru_map@0.3.3: {} - lz-string@1.5.0: {} make-dir@2.1.0: @@ -29298,12 +26608,6 @@ snapshots: inherits: 2.0.4 safe-buffer: 5.2.1 - md5@2.3.0: - dependencies: - charenc: 0.0.2 - crypt: 0.0.2 - is-buffer: 1.1.6 - mdast-builder@1.1.1: dependencies: '@types/unist': 2.0.8 @@ -29534,18 +26838,6 @@ snapshots: media-typer@1.1.0: {} - mem@4.3.0: - dependencies: - map-age-cleaner: 0.1.3 - mimic-fn: 2.1.0 - p-is-promise: 2.1.0 - - mem@5.1.1: - dependencies: - map-age-cleaner: 0.1.3 - mimic-fn: 2.1.0 - p-is-promise: 2.1.0 - mem@8.1.1: dependencies: map-age-cleaner: 0.1.3 @@ -29586,15 +26878,6 @@ snapshots: optionalDependencies: '@types/node': 20.12.8 - method-override@3.0.0: - dependencies: - debug: 3.1.0 - methods: 1.1.2 - parseurl: 1.3.3 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - methods@1.1.2: {} microevent.ts@0.1.1: {} @@ -30111,8 +27394,6 @@ snapshots: dependencies: minimist: 1.2.8 - mkdirp@1.0.4: {} - mocha@10.3.0: dependencies: ansi-colors: 4.1.1 @@ -30161,10 +27442,6 @@ snapshots: module-details-from-path@1.0.3: {} - moment-timezone@0.5.33: - dependencies: - moment: 2.29.3 - moment@2.29.3: {} monaco-editor-webpack-plugin@7.0.1(monaco-editor@0.33.0)(webpack@5.90.3): @@ -30180,16 +27457,6 @@ snapshots: '@types/whatwg-url': 11.0.5 whatwg-url: 13.0.0 - mongodb@3.6.9: - dependencies: - bl: 2.2.1 - bson: 1.1.6 - denque: 1.5.1 - optional-require: 1.1.8 - safe-buffer: 5.2.1 - optionalDependencies: - saslprep: 1.0.3 - mongodb@6.10.0(@aws-sdk/credential-providers@3.521.0)(socks@2.8.3): dependencies: '@mongodb-js/saslprep': 1.1.9 @@ -30219,29 +27486,10 @@ snapshots: ms@2.0.0: {} - ms@2.1.1: {} - ms@2.1.2: {} ms@2.1.3: {} - msgpack-js@0.3.0: - dependencies: - bops: 0.0.7 - - msgpack-stream@0.0.13: - dependencies: - bops: 1.0.0 - msgpack-js: 0.3.0 - through: 2.3.4 - - msgpack5@4.5.1: - dependencies: - bl: 2.2.1 - inherits: 2.0.4 - readable-stream: 2.3.8 - safe-buffer: 5.2.1 - msgpackr-extract@3.0.2: dependencies: node-gyp-build-optional-packages: 5.0.7 @@ -30298,20 +27546,6 @@ snapshots: mute-stream@2.0.0: {} - mux-demux@3.7.9: - dependencies: - duplex: 1.0.0 - json-buffer: 2.0.11 - msgpack-stream: 0.0.13 - stream-combiner: 0.0.2 - stream-serializer: 1.1.2 - through: 2.3.8 - xtend: 1.0.3 - - nanoid@2.1.11: {} - - nanoid@3.3.4: {} - nanoid@3.3.7: {} nanoid@3.3.9: {} @@ -30370,19 +27604,12 @@ snapshots: no-profanity@1.5.1: {} - nocache@2.1.0: {} - node-abi@3.47.0: dependencies: semver: 7.6.0 node-addon-api@4.3.0: {} - node-environment-flags@1.0.6: - dependencies: - object.getownpropertydescriptors: 2.1.7 - semver: 5.7.2 - node-eta@0.9.0: {} node-fetch@2.6.1: {} @@ -30404,38 +27631,8 @@ snapshots: node-releases@2.0.14: {} - nodemailer-direct-transport@3.3.2: - dependencies: - nodemailer-shared: 1.1.0 - smtp-connection: 2.12.0 - - nodemailer-fetch@1.6.0: {} - - nodemailer-ses-transport@1.5.1: - dependencies: - aws-sdk: 2.1467.0 - - nodemailer-shared@1.1.0: - dependencies: - nodemailer-fetch: 1.6.0 - - nodemailer-stub-transport@1.1.0: {} - nodemailer@6.9.10: {} - nodemon@2.0.16: - dependencies: - chokidar: 3.5.3 - debug: 3.2.7(supports-color@5.5.0) - ignore-by-default: 1.0.1 - minimatch: 3.1.2 - pstree.remy: 1.1.8 - semver: 5.7.2 - supports-color: 5.5.0 - touch: 3.1.0 - undefsafe: 2.0.5 - update-notifier: 5.1.0 - nopt@1.0.10: dependencies: abbrev: 1.1.1 @@ -30495,10 +27692,6 @@ snapshots: nwsapi@2.2.7: {} - oauth-sign@0.9.0: {} - - oauth@0.9.15: {} - object-assign@4.1.1: {} object-copy@0.1.0: @@ -30565,14 +27758,6 @@ snapshots: es-abstract: 1.23.9 es-object-atoms: 1.1.1 - object.getownpropertydescriptors@2.1.7: - dependencies: - array.prototype.reduce: 1.0.6 - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.22.2 - safe-array-concat: 1.0.1 - object.groupby@1.0.1: dependencies: call-bind: 1.0.7 @@ -30656,10 +27841,6 @@ snapshots: opentracing@0.14.7: {} - optional-require@1.1.8: - dependencies: - require-at: 1.0.6 - optionator@0.9.3: dependencies: '@aashutoshrathi/word-wrap': 1.2.6 @@ -30669,8 +27850,6 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - options@0.0.6: {} - ora@5.4.1: dependencies: bl: 4.1.0 @@ -30687,18 +27866,6 @@ snapshots: os-browserify@0.3.0: {} - os-locale@3.1.0: - dependencies: - execa: 1.0.0 - lcid: 2.0.0 - mem: 4.3.0 - - os-locale@5.0.0: - dependencies: - execa: 4.1.0 - lcid: 3.1.1 - mem: 5.1.1 - os-tmpdir@1.0.2: {} outvariant@1.4.3: {} @@ -30719,8 +27886,6 @@ snapshots: p-finally@1.0.0: {} - p-is-promise@2.1.0: {} - p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -30845,8 +28010,6 @@ snapshots: unist-util-modify-children: 2.0.0 unist-util-visit-children: 1.1.4 - parse-passwd@1.0.0: {} - parse-path@4.0.4: dependencies: is-ssh: 1.4.0 @@ -30887,51 +28050,6 @@ snapshots: pascalcase@0.1.1: {} - passport-auth0@1.4.2(debug@2.2.0): - dependencies: - axios: 0.22.0(debug@2.2.0) - passport-oauth: 1.0.0 - passport-oauth2: 1.7.0 - transitivePeerDependencies: - - debug - - passport-local@1.0.0: - dependencies: - passport-strategy: 1.0.0 - - passport-mock-strategy@2.0.0: - dependencies: - '@types/express': 4.17.18 - '@types/passport': 1.0.13 - es6-promise: 4.2.8 - passport: 0.4.1 - - passport-oauth1@1.3.0: - dependencies: - oauth: 0.9.15 - passport-strategy: 1.0.0 - utils-merge: 1.0.1 - - passport-oauth2@1.7.0: - dependencies: - base64url: 3.0.1 - oauth: 0.9.15 - passport-strategy: 1.0.0 - uid2: 0.0.4 - utils-merge: 1.0.1 - - passport-oauth@1.0.0: - dependencies: - passport-oauth1: 1.3.0 - passport-oauth2: 1.7.0 - - passport-strategy@1.0.0: {} - - passport@0.4.1: - dependencies: - passport-strategy: 1.0.0 - pause: 0.0.1 - password-prompt@1.1.3: dependencies: ansi-escapes: 4.3.2 @@ -30973,8 +28091,6 @@ snapshots: dependencies: through: 2.3.8 - pause@0.0.1: {} - pbkdf2@3.1.2: dependencies: create-hash: 1.2.0 @@ -31467,8 +28583,6 @@ snapshots: psl@1.9.0: {} - pstree.remy@1.1.8: {} - public-encrypt@4.0.3: dependencies: bn.js: 4.12.0 @@ -31485,8 +28599,6 @@ snapshots: punycode.js@2.3.1: {} - punycode@1.3.2: {} - punycode@1.4.1: {} punycode@2.3.0: {} @@ -31573,8 +28685,6 @@ snapshots: querystring-es3@0.2.1: {} - querystring@0.2.0: {} - querystring@0.2.1: {} querystringify@2.2.0: {} @@ -31598,8 +28708,6 @@ snapshots: discontinuous-range: 1.0.0 ret: 0.1.15 - random-bytes@1.0.0: {} - randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -31613,19 +28721,6 @@ snapshots: range-parser@1.2.1: {} - rate-limit-mongo@2.3.2: - dependencies: - mongodb: 3.6.9 - twostep: 0.4.2 - underscore: 1.12.1 - transitivePeerDependencies: - - aws4 - - bson-ext - - kerberos - - mongodb-client-encryption - - mongodb-extjson - - snappy - raw-body@2.5.1: dependencies: bytes: 3.1.2 @@ -32023,8 +29118,6 @@ snapshots: dependencies: '@babel/runtime': 7.23.9 - referrer-policy@1.2.0: {} - reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -32053,8 +29146,6 @@ snapshots: regenerate@1.4.2: {} - regenerator-runtime@0.13.11: {} - regenerator-runtime@0.14.0: {} regenerator-transform@0.15.2: @@ -32235,31 +29326,6 @@ snapshots: replace-ext@1.0.0: {} - request@2.88.2: - dependencies: - aws-sign2: 0.7.0 - aws4: 1.12.0 - caseless: 0.12.0 - combined-stream: 1.0.8 - extend: 3.0.2 - forever-agent: 0.6.1 - form-data: 2.3.3 - har-validator: 5.1.5 - http-signature: 1.2.0 - is-typedarray: 1.0.0 - isstream: 0.1.2 - json-stringify-safe: 5.0.1 - mime-types: 2.1.35 - oauth-sign: 0.9.0 - performance-now: 2.1.0 - qs: 6.5.3 - safe-buffer: 5.2.1 - tough-cookie: 2.5.0 - tunnel-agent: 0.6.0 - uuid: 3.4.0 - - require-at@1.0.6: {} - require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -32359,8 +29425,6 @@ snapshots: hash-base: 3.1.0 inherits: 2.0.4 - rndm@1.2.0: {} - rollup@3.29.4: optionalDependencies: fsevents: 2.3.3 @@ -32390,8 +29454,6 @@ snapshots: dependencies: queue-microtask: 1.2.3 - rx@4.1.0: {} - rxjs@6.6.7: dependencies: tslib: 1.14.1 @@ -32419,8 +29481,6 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 - safe-buffer@5.1.1: {} - safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} @@ -32470,17 +29530,8 @@ snapshots: parse-srcset: 1.0.2 postcss: 8.4.35 - saslprep@1.0.3: - dependencies: - sparse-bitfield: 3.0.3 - optional: true - sass.js@0.11.1: {} - sax@1.2.1: {} - - sax@1.3.0: {} - saxes@5.0.1: dependencies: xmlchars: 2.2.0 @@ -32584,14 +29635,6 @@ snapshots: dependencies: randombytes: 2.1.0 - serve-favicon@2.5.0: - dependencies: - etag: 1.8.1 - fresh: 0.5.2 - ms: 2.1.1 - parseurl: 1.3.3 - safe-buffer: 5.1.1 - serve-handler@6.1.3: dependencies: bytes: 3.0.0 @@ -32697,8 +29740,6 @@ snapshots: setprototypeof@1.1.0: {} - setprototypeof@1.1.1: {} - setprototypeof@1.2.0: {} sha.js@2.4.11: @@ -32753,10 +29794,6 @@ snapshots: shimmer@1.2.1: {} - shortid@2.2.16: - dependencies: - nanoid: 2.1.11 - shx@0.3.4: dependencies: minimist: 1.2.8 @@ -32835,8 +29872,6 @@ snapshots: sisteransi@1.0.5: {} - slash@2.0.0: {} - slash@3.0.0: {} slash@4.0.0: {} @@ -32862,11 +29897,6 @@ snapshots: smart-buffer@4.2.0: {} - smtp-connection@2.12.0: - dependencies: - httpntlm: 1.6.1 - nodemailer-shared: 1.1.0 - snapdragon-node@2.1.1: dependencies: define-property: 1.0.0 @@ -33026,22 +30056,6 @@ snapshots: sprintf-js@1.1.3: {} - sse@0.0.8: - dependencies: - options: 0.0.6 - - sshpk@1.17.0: - dependencies: - asn1: 0.2.6 - assert-plus: 1.0.0 - bcrypt-pbkdf: 1.0.2 - dashdash: 1.14.1 - ecc-jsbn: 0.1.2 - getpass: 0.1.7 - jsbn: 0.1.1 - safer-buffer: 2.1.2 - tweetnacl: 0.14.5 - st@2.0.0: dependencies: async-cache: 1.1.0 @@ -33095,10 +30109,6 @@ snapshots: duplexer2: 0.1.4 readable-stream: 2.3.8 - stream-combiner@0.0.2: - dependencies: - duplexer: 0.0.4 - stream-combiner@0.2.2: dependencies: duplexer: 0.1.2 @@ -33111,8 +30121,6 @@ snapshots: readable-stream: 3.6.2 xtend: 4.0.2 - stream-serializer@1.1.2: {} - stream-splicer@2.0.1: dependencies: inherits: 2.0.4 @@ -33313,90 +30321,8 @@ snapshots: '@types/node': 20.12.8 qs: 6.11.2 - stripe@8.205.0: - dependencies: - '@types/node': 20.8.0 - qs: 6.11.2 - strnum@1.0.5: {} - strong-error-handler@3.5.0: - dependencies: - '@types/express': 4.17.18 - accepts: 1.3.8 - debug: 4.3.4(supports-color@8.1.1) - ejs: 3.1.9 - fast-safe-stringify: 2.1.1 - http-status: 1.7.0 - js2xmlparser: 4.0.2 - strong-globalize: 6.0.6 - transitivePeerDependencies: - - supports-color - - strong-globalize@4.1.3: - dependencies: - accept-language: 3.0.18 - debug: 4.3.4(supports-color@8.1.1) - globalize: 1.7.0 - lodash: 4.17.21 - md5: 2.3.0 - mkdirp: 0.5.6 - os-locale: 3.1.0 - yamljs: 0.3.0 - transitivePeerDependencies: - - supports-color - - strong-globalize@5.1.0: - dependencies: - accept-language: 3.0.18 - debug: 4.3.4(supports-color@8.1.1) - globalize: 1.7.0 - lodash: 4.17.21 - md5: 2.3.0 - mkdirp: 0.5.6 - os-locale: 5.0.0 - yamljs: 0.3.0 - transitivePeerDependencies: - - supports-color - - strong-globalize@6.0.6: - dependencies: - accept-language: 3.0.18 - debug: 4.3.4(supports-color@8.1.1) - globalize: 1.7.0 - lodash: 4.17.21 - md5: 2.3.0 - mkdirp: 1.0.4 - os-locale: 5.0.0 - yamljs: 0.3.0 - transitivePeerDependencies: - - supports-color - - strong-remoting@3.17.0: - dependencies: - async: 3.2.4 - body-parser: 1.20.0 - debug: 4.3.4(supports-color@8.1.1) - depd: 2.0.0 - escape-string-regexp: 2.0.0 - eventemitter2: 5.0.1 - express: 4.18.2 - inflection: 1.13.4 - jayson: 2.1.2 - js2xmlparser: 3.0.0 - loopback-datatype-geopoint: 1.0.0 - loopback-phase: 3.4.0 - mux-demux: 3.7.9 - qs: 6.11.2 - request: 2.88.2 - sse: 0.0.8 - strong-error-handler: 3.5.0 - strong-globalize: 5.1.0 - traverse: 0.6.7 - xml2js: 0.4.23 - transitivePeerDependencies: - - supports-color - strtok3@6.3.0: dependencies: '@tokenizer/token': 0.3.0 @@ -33533,8 +30459,6 @@ snapshots: picocolors: 1.1.1 stable: 0.1.8 - swagger-ui@2.2.10: {} - symbol-observable@1.2.0: {} symbol-tree@3.2.4: {} @@ -33659,8 +30583,6 @@ snapshots: readable-stream: 2.3.8 xtend: 4.0.2 - through@2.3.4: {} - through@2.3.8: {} timers-browserify@1.4.2: @@ -33718,8 +30640,6 @@ snapshots: dependencies: to-no-case: 1.0.2 - to-utf8@0.0.1: {} - to-vfile@5.0.3: dependencies: is-buffer: 2.0.5 @@ -33741,19 +30661,8 @@ snapshots: standardized-audio-context: 25.3.57 tslib: 2.6.2 - toposort@2.0.2: {} - totalist@3.0.1: {} - touch@3.1.0: - dependencies: - nopt: 1.0.10 - - tough-cookie@2.5.0: - dependencies: - psl: 1.9.0 - punycode: 2.3.0 - tough-cookie@4.1.3: dependencies: psl: 1.9.0 @@ -33782,8 +30691,6 @@ snapshots: dependencies: punycode: 2.3.0 - traverse@0.6.7: {} - trim-trailing-lines@1.1.4: {} trim@0.0.1: {} @@ -33885,8 +30792,6 @@ snapshots: tslib@2.6.2: {} - tsscmp@1.0.6: {} - tsutils@3.21.0(typescript@5.2.2): dependencies: tslib: 1.14.1 @@ -33905,10 +30810,6 @@ snapshots: dependencies: safe-buffer: 5.2.1 - tweetnacl@0.14.5: {} - - twostep@0.4.2: {} - type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -34038,14 +30939,6 @@ snapshots: uc.micro@2.0.0: {} - uid-safe@2.1.5: - dependencies: - random-bytes: 1.0.0 - - uid2@0.0.3: {} - - uid2@0.0.4: {} - umd@3.0.3: {} unbox-primitive@1.0.2: @@ -34077,19 +30970,11 @@ snapshots: simple-concat: 1.0.1 xtend: 4.0.2 - undefsafe@2.0.5: {} - underscore.string@3.3.6: dependencies: sprintf-js: 1.1.3 util-deprecate: 1.0.2 - underscore@1.12.1: {} - - underscore@1.13.6: {} - - underscore@1.7.0: {} - undici-types@5.26.5: {} unherit@1.1.3: @@ -34347,11 +31232,6 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 - url@0.10.3: - dependencies: - punycode: 1.3.2 - querystring: 0.2.0 - url@0.11.3: dependencies: punycode: 1.4.1 @@ -34387,8 +31267,6 @@ snapshots: uuid@7.0.3: {} - uuid@8.0.0: {} - uuid@8.3.2: {} uuid@9.0.1: {} @@ -34411,10 +31289,6 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.4 convert-source-map: 1.9.0 - v8flags@3.2.0: - dependencies: - homedir-polyfill: 1.0.3 - valid-url@1.0.9: {} validate-npm-package-license@3.0.4: @@ -34424,18 +31298,10 @@ snapshots: validator@13.11.0: {} - validator@13.7.0: {} - value-or-promise@1.0.6: {} vary@1.1.2: {} - verror@1.10.0: - dependencies: - assert-plus: 1.0.0 - core-util-is: 1.0.2 - extsprintf: 1.3.0 - vfile-location@2.0.6: {} vfile-location@3.2.0: {} @@ -34587,7 +31453,7 @@ snapshots: webpack-virtual-modules@0.3.2: dependencies: - debug: 3.2.7(supports-color@5.5.0) + debug: 3.2.7 transitivePeerDependencies: - supports-color @@ -34809,32 +31675,14 @@ snapshots: x-is-string@0.1.0: {} - x-xss-protection@1.3.0: {} - xdg-basedir@4.0.0: {} xml-name-validator@3.0.0: {} xml-name-validator@4.0.0: {} - xml2js@0.4.23: - dependencies: - sax: 1.3.0 - xmlbuilder: 11.0.1 - - xml2js@0.5.0: - dependencies: - sax: 1.2.1 - xmlbuilder: 11.0.1 - - xmlbuilder@11.0.1: {} - xmlchars@2.2.0: {} - xmlcreate@1.0.2: {} - - xmlcreate@2.0.4: {} - xmlhttprequest-ssl@1.6.3: {} xss@1.0.14: @@ -34844,8 +31692,6 @@ snapshots: xstate@4.38.2: {} - xtend@1.0.3: {} - xtend@4.0.2: {} xterm-addon-fit@0.8.0(xterm@5.2.1): @@ -34873,11 +31719,6 @@ snapshots: yaml@2.3.2: {} - yamljs@0.3.0: - dependencies: - argparse: 1.0.10 - glob: 7.2.3 - yargs-parser@18.1.3: dependencies: camelcase: 5.3.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index bfae73248bd..989a1c773c3 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,5 @@ packages: - 'api' - - 'api-server' - 'client' - 'curriculum' - 'shared' diff --git a/tools/scripts/lint/validate-keys.ts b/tools/scripts/lint/validate-keys.ts index 3f12209fcf0..382786796e1 100644 --- a/tools/scripts/lint/validate-keys.ts +++ b/tools/scripts/lint/validate-keys.ts @@ -70,37 +70,34 @@ const readComponentCode = (filePath: string): string => { const clientCodebase: string = readComponentCode( path.join(process.cwd() + '/src') ); -const serverCodebase: string = readComponentCode( - path.join(process.cwd() + '/../api-server/src/server') -); for (const key of translationKeys) { - if (!clientCodebase.includes(key) && !serverCodebase.includes(key)) { + if (!clientCodebase.includes(key)) { console.warn(`The translation key '${key}' appears to be unused.`); } } for (const key of motivationKeys) { - if (!clientCodebase.includes(key) && !serverCodebase.includes(key)) { + if (!clientCodebase.includes(key)) { console.warn(`The motivation key '${key}' appears to be unused.`); } } for (const key of metaKeys) { - if (!clientCodebase.includes(key) && !serverCodebase.includes(key)) { + if (!clientCodebase.includes(key)) { console.warn(`The meta key '${key}' appears to be unused.`); } } for (const key of introKeys) { - if (!clientCodebase.includes(key) && !serverCodebase.includes(key)) { + if (!clientCodebase.includes(key)) { console.warn(`The intro key '${key}' appears to be unused.`); } } for (const key of trendingKeys) { - if (!clientCodebase.includes(key) && !serverCodebase.includes(key)) { + if (!clientCodebase.includes(key)) { console.warn(`The trending key '${key}' appears to be unused.`); } } for (const key of linksKeys) { - if (!clientCodebase.includes(key) && !serverCodebase.includes(key)) { + if (!clientCodebase.includes(key)) { console.warn(`The links key '${key}' appears to be unused.`); } }